Middleware Node.js Stabil di Next.js 15.5: Panduan Server-Side Processing Tingkat Lanjut

Revolusi Middleware Next.js

Kalau kamu sudah lama berkecimpung dengan Next.js, pasti merasakan bagaimana middleware sebelumnya berjalan di Edge Runtime. Nah, sekarang ada kabar gembira nih! Next.js 15.5 sudah resmi mendukung Node.js Runtime untuk middleware, dan ini benar-benar game changer banget untuk developer yang butuh akses lebih dalam ke Node.js API.

Perubahan ini sebenernya sudah ditunggu-tunggu sejak lama, soalnya Edge Runtime memang punya beberapa keterbatasan yang bikin developer agak kesulitan. Misalnya saja, kalau kamu mau akses sistem file atau koneksi database langsung dari middleware, bakal ketemu tembok. Sekarang dengan Node.js Runtime, semua API Node.js asli bisa dipake dengan leluasa.

Yang menarik dari pembaharuan ini adalah tim Next.js tidak langsung menghapus Edge Runtime. Mereka bikin transisi yang halus dengan memberikan pilihan runtime yang bisa dikonfigurasi sesuai kebutuhan proyek. Ini strategi yang cerdas banget, karena beberapa aplikasi mungkin masih cocok pakai Edge Runtime untuk performa yang lebih optimal.

Dari segi arsitektur, middleware sekarang bisa akses semua paket Node.js yang sebelumnya tidak tersedia di Edge Runtime. Database driver seperti MySQL, PostgreSQL, atau MongoDB client sekarang bisa dipake langsung di middleware tanpa solusi rumit. Operasi file juga jadi lebih mudah, kamu bisa baca-tulis file konfigurasi atau logging dengan modul fs asli.

Breaking Changes dan Strategi Migrasi

Transisi dari Edge ke Node.js Runtime tidak selalu mulus, ada beberapa perubahan besar yang perlu diperhatikan. Yang paling signifikan adalah perubahan di environment variables dan cara menangani objek request/response.

Pertama-tama, environment variables yang sebelumnya tersedia secara otomatis di Edge Runtime sekarang mungkin perlu import atau konfigurasi eksplisit. Contohnya, kalau sebelumnya kamu bisa langsung akses process.env di middleware Edge, sekarang di Node.js Runtime ada beberapa kasus khusus yang mungkin bikin nilai jadi undefined.

// Sebelum (Edge Runtime)
export function middleware(request) {
  const apiKey = process.env.API_KEY; // Mungkin undefined
  // logika lainnya
}

// Sesudah (Node.js Runtime)
import { NextResponse } from 'next/server';

export function middleware(request) {
  // Pastikan environment variables dimuat dengan benar
  const apiKey = process.env.API_KEY;

  if (!apiKey) {
    console.warn('API key tidak ditemukan di middleware');
    return NextResponse.redirect(new URL('/error', request.url));
  }

  // Logika middleware kamu di sini
  return NextResponse.next();
}

// Tentukan runtime
export const config = {
  runtime: 'nodejs',
  matcher: '/api/:path*'
};

Response headers juga ada sedikit perbedaan penanganan. Di Edge Runtime, beberapa headers secara otomatis di-set, tapi di Node.js Runtime kamu harus lebih eksplisit. Ini penting banget untuk konfigurasi CORS atau header khusus yang diperlukan aplikasi.

Strategi migrasi yang paling aman adalah migrasi bertahap. Mulai dari middleware yang sederhana dulu, test secara menyeluruh di lingkungan development, baru lanjut ke middleware yang lebih kompleks. Disarankan bikin backup dari konfigurasi middleware lama sebelum mulai migrasi.

Penggunaan memori juga berubah signifikan. Node.js Runtime umumnya menggunakan memori lebih banyak dibanding Edge Runtime, tapi memberikan fleksibilitas yang lebih besar. Kalau aplikasi kamu terbatas memori, perlu pertimbangan tambahan untuk alokasi resource.

Dampak Terhadap Aplikasi yang Sudah Ada

Aplikasi yang sudah berjalan di production pasti akan terpengaruh sama perubahan ini, tapi seberapa besar dampaknya tergantung sama kompleksitas middleware yang sudah ada. Aplikasi yang pakai middleware sederhana untuk routing atau autentikasi dasar mungkin tidak akan merasakan perbedaan yang signifikan.

Tapi kalau aplikasi kamu sangat bergantung pada perilaku spesifik Edge Runtime, mungkin akan ada beberapa penyesuaian yang diperlukan. Karakteristik performa juga bisa berubah - beberapa operasi mungkin jadi lebih lambat, tapi yang lain bisa jadi lebih cepat karena akses langsung ke API Node.js.

Koneksi database adalah salah satu area yang paling terpengaruh. Sebelumnya, kamu mungkin harus pakai database serverless atau panggilan API untuk akses data dari middleware. Sekarang bisa langsung bikin koneksi database dengan driver apapun yang kompatibel dengan Node.js.

// Contoh: Akses database langsung di middleware
import { NextResponse } from 'next/server';

export async function middleware(request) {
  try {
    const userId = request.cookies.get('user_id')?.value;

    if (userId) {
      // Cek role user dari database (contoh sederhana)
      const userRole = await checkUserRole(userId);

      if (userRole === 'admin') {
        // Izinkan akses ke rute admin
        return NextResponse.next();
      }
    }

    // Redirect user non-admin
    return NextResponse.redirect(new URL('/login', request.url));

  } catch (error) {
    console.error('Error middleware database:', error);
    return NextResponse.redirect(new URL('/error', request.url));
  }
}

// Fungsi helper sederhana
async function checkUserRole(userId) {
  // Implementasi cek database di sini
  // Return role user
  return 'user'; // contoh
}

export const config = {
  runtime: 'nodejs',
  matcher: '/admin/:path*'
};

Logging dan monitoring juga jadi lebih powerful. Sekarang bisa pakai library logging yang lengkap seperti Winston atau Pino langsung di middleware. Sebelumnya di Edge Runtime, pilihan logging agak terbatas.

Strategi caching mungkin perlu di-review juga. Node.js Runtime middleware bisa akses Redis atau memcached langsung, yang membuka kemungkinan untuk mekanisme caching yang lebih canggih. Tapi ini juga berarti kamu harus kelola connection pooling dan pembersihan resource dengan lebih hati-hati.

Tools monitoring performa yang sebelumnya tidak kompatibel dengan Edge Runtime sekarang bisa dipake. APM tools seperti New Relic atau DataDog bisa memberikan wawasan lebih detail tentang performa middleware.

Implikasi keamanan juga perlu dipertimbangkan. Dengan akses penuh ke API Node.js, middleware sekarang punya permukaan serangan yang lebih luas. Validasi input dan sanitasi jadi lebih kritis, terutama kalau middleware menangani operasi sensitif seperti autentikasi atau autorisasi.

Fitur-Fitur Baru Middleware Node.js

image.png

Sekarang kita masuk ke bagian yang paling seru nih - fitur-fitur baru yang bisa kamu manfaatkan dengan middleware Node.js di Next.js 15.5. Perubahan ini bukan cuma sekedar upgrade biasa, tapi membuka pintu ke kemungkinan-kemungkinan baru yang sebelumnya mustahil dilakukan di Edge Runtime.

Yang paling kentara adalah akses penuh ke ekosistem Node.js yang lengkap. Sebelumnya kamu terbatas sama API yang tersedia di Edge Runtime, sekarang bisa pakai hampir semua library Node.js yang ada. Ini berarti kamu bisa implementasi logika bisnis yang lebih kompleks langsung di layer middleware.

Akses Penuh ke Node.js APIs

Dengan dukungan penuh Node.js Runtime, middleware sekarang bisa menggunakan semua API native Node.js tanpa batasan. Ini termasuk modul-modul penting seperti fs, path, crypto, buffer, dan masih banyak lagi yang sebelumnya tidak bisa diakses.

Contoh paling sederhana adalah penggunaan modul crypto untuk generate token atau hash password langsung di middleware. Sebelumnya kamu harus pakai library external atau workaround yang ribet.

import { NextResponse } from 'next/server';
import crypto from 'crypto';

export function middleware(request) {
  // Generate unique request ID menggunakan crypto
  const requestId = crypto.randomUUID();

  // Tambahkan ke header untuk tracking
  const response = NextResponse.next();
  response.headers.set('X-Request-ID', requestId);
  response.headers.set('X-Timestamp', Date.now().toString());

  console.log(`Request ${requestId} processed at ${new Date().toISOString()}`);

  return response;
}

export const config = {
  runtime: 'nodejs',
  matcher: '/api/:path*'
};

API url dan querystring juga sekarang bisa dipake dengan lebih fleksibel. Parsing URL kompleks atau manipulasi query parameters jadi lebih mudah dan performant. Ini berguna banget kalau kamu bikin middleware untuk routing dinamis atau parameter processing.

Modul os memberikan informasi sistem yang bisa berguna untuk monitoring atau debugging. Kamu bisa dapat info tentang memory usage, CPU info, atau network interfaces langsung dari middleware. Ini berguna untuk implementasi health check atau system monitoring.

import { NextResponse } from 'next/server';
import os from 'os';

export function middleware(request) {
  const pathname = request.nextUrl.pathname;

  // Health check endpoint
  if (pathname === '/api/health') {
    const healthInfo = {
      status: 'ok',
      timestamp: new Date().toISOString(),
      memory: {
        free: Math.round(os.freemem() / 1024 / 1024) + ' MB',
        total: Math.round(os.totalmem() / 1024 / 1024) + ' MB'
      },
      uptime: Math.round(os.uptime()) + ' seconds'
    };

    return NextResponse.json(healthInfo);
  }

  return NextResponse.next();
}

export const config = {
  runtime: 'nodejs',
  matcher: '/api/:path*'
};

Operasi File System dalam Middleware

Salah satu fitur paling powerful dari middleware Node.js adalah kemampuan untuk melakukan operasi file system langsung. Ini membuka banyak kemungkinan baru untuk caching, logging, configuration management, dan dynamic content generation.

Operasi baca file adalah yang paling umum digunakan. Misalnya, kamu bisa baca file konfigurasi, template, atau data static langsung dari middleware tanpa perlu hit database atau external API. Ini sangat berguna untuk aplikasi yang butuh konfigurasi dinamis.

import { NextResponse } from 'next/server';
import fs from 'fs';
import path from 'path';

export async function middleware(request) {
  try {
    const configPath = path.join(process.cwd(), 'config', 'app-config.json');

    // Baca konfigurasi aplikasi
    const configData = await fs.promises.readFile(configPath, 'utf8');
    const config = JSON.parse(configData);

    // Cek apakah maintenance mode aktif
    if (config.maintenanceMode) {
      return NextResponse.redirect(new URL('/maintenance', request.url));
    }

    // Cek feature flags
    const userAgent = request.headers.get('user-agent') || '';
    const isMobile = /Mobile|Android|iPhone/i.test(userAgent);

    if (isMobile && !config.mobileEnabled) {
      return NextResponse.redirect(new URL('/mobile-not-supported', request.url));
    }

    return NextResponse.next();

  } catch (error) {
    console.error('Error reading config:', error);
    // Fallback kalau ada error
    return NextResponse.next();
  }
}

export const config = {
  runtime: 'nodejs',
  matcher: '/((?!_next/static|_next/image|favicon.ico).*)'
};

Operasi tulis file juga sangat berguna untuk logging atau caching. Kamu bisa bikin sistem logging custom yang nulis ke file secara real-time, atau implementasi cache sederhana yang nyimpen data ke file system.

Untuk aplikasi BuildWithAngga, contoh praktisnya adalah tracking user activity atau analytics data. Middleware bisa nulis log setiap request ke file, yang nanti bisa diproses untuk mendapatkan insight tentang penggunaan aplikasi.

import { NextResponse } from 'next/server';
import fs from 'fs';
import path from 'path';

export async function middleware(request) {
  const logPath = path.join(process.cwd(), 'logs', 'access.log');

  // Data yang mau di-log
  const logData = {
    timestamp: new Date().toISOString(),
    method: request.method,
    url: request.url,
    userAgent: request.headers.get('user-agent'),
    ip: request.headers.get('x-forwarded-for') || 'unknown'
  };

  try {
    // Pastikan direktori logs ada
    const logsDir = path.dirname(logPath);
    await fs.promises.mkdir(logsDir, { recursive: true });

    // Append log data ke file
    await fs.promises.appendFile(
      logPath,
      JSON.stringify(logData) + '\\n'
    );
  } catch (error) {
    console.error('Error writing log:', error);
  }

  return NextResponse.next();
}

export const config = {
  runtime: 'nodejs',
  matcher: '/((?!_next/static|_next/image|favicon.ico).*)'
};

Implementasi Autentikasi Kompleks

Dengan middleware Node.js, implementasi sistem autentikasi yang kompleks jadi jauh lebih mudah dan fleksibel. Kamu bisa bikin multi-layer authentication, role-based access control, atau bahkan custom authentication scheme yang sesuai dengan kebutuhan aplikasi.

JWT (JSON Web Token) processing sekarang bisa dilakukan langsung di middleware dengan library apapun yang kamu suka. Verifikasi signature, decode payload, dan validasi claims semua bisa dilakukan dengan performa yang optimal.

import { NextResponse } from 'next/server';
import jwt from 'jsonwebtoken';

export function middleware(request) {
  const token = request.cookies.get('auth-token')?.value;
  const pathname = request.nextUrl.pathname;

  // Route yang butuh autentikasi
  const protectedRoutes = ['/dashboard', '/profile', '/admin'];
  const isProtectedRoute = protectedRoutes.some(route =>
    pathname.startsWith(route)
  );

  if (!isProtectedRoute) {
    return NextResponse.next();
  }

  // Cek keberadaan token
  if (!token) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  try {
    // Verifikasi JWT token
    const decoded = jwt.verify(token, process.env.JWT_SECRET);

    // Cek role untuk admin routes
    if (pathname.startsWith('/admin') && decoded.role !== 'admin') {
      return NextResponse.redirect(new URL('/unauthorized', request.url));
    }

    // Cek expiry time
    const now = Math.floor(Date.now() / 1000);
    if (decoded.exp < now) {
      // Token expired, redirect ke login
      const response = NextResponse.redirect(new URL('/login', request.url));
      response.cookies.delete('auth-token');
      return response;
    }

    // Add user info ke headers untuk digunakan di components
    const response = NextResponse.next();
    response.headers.set('X-User-ID', decoded.userId);
    response.headers.set('X-User-Role', decoded.role);

    return response;

  } catch (error) {
    console.error('JWT verification error:', error);
    // Token tidak valid, hapus cookie dan redirect
    const response = NextResponse.redirect(new URL('/login', request.url));
    response.cookies.delete('auth-token');
    return response;
  }
}

export const config = {
  runtime: 'nodejs',
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)']
};

Session-based authentication juga bisa diimplementasi dengan lebih robust. Kamu bisa koneksi langsung ke session store seperti Redis atau database untuk validasi session yang real-time.

Rate limiting adalah contoh lain dari autentikasi kompleks yang sekarang mudah diimplementasi. Dengan akses ke file system atau external storage, kamu bisa bikin sistem rate limiting yang sophisticated dengan berbagai strategi seperti sliding window atau token bucket.

import { NextResponse } from 'next/server';

// Simple in-memory rate limiter (untuk demo)
const rateLimitStore = new Map();

export function middleware(request) {
  const ip = request.headers.get('x-forwarded-for') || 'unknown';
  const now = Date.now();
  const windowMs = 60 * 1000; // 1 menit
  const maxRequests = 100; // 100 requests per menit

  // Get atau create entry untuk IP ini
  if (!rateLimitStore.has(ip)) {
    rateLimitStore.set(ip, {
      requests: [],
      blocked: false
    });
  }

  const clientData = rateLimitStore.get(ip);

  // Hapus requests yang sudah lewat window
  clientData.requests = clientData.requests.filter(
    timestamp => now - timestamp < windowMs
  );

  // Cek apakah melebihi limit
  if (clientData.requests.length >= maxRequests) {
    return NextResponse.json(
      { error: 'Too many requests' },
      {
        status: 429,
        headers: {
          'Retry-After': '60'
        }
      }
    );
  }

  // Tambahkan request baru
  clientData.requests.push(now);
  rateLimitStore.set(ip, clientData);

  // Add rate limit headers
  const response = NextResponse.next();
  response.headers.set('X-RateLimit-Limit', maxRequests.toString());
  response.headers.set('X-RateLimit-Remaining',
    (maxRequests - clientData.requests.length).toString()
  );

  return response;
}

export const config = {
  runtime: 'nodejs',
  matcher: '/api/:path*'
};

Multi-factor authentication (MFA) verification juga sekarang bisa dihandle langsung di middleware. Integrasi dengan provider seperti Google Authenticator, SMS gateway, atau email verification bisa dilakukan dengan library Node.js apapun yang dibutuhkan.

Panduan Implementasi Praktis

Setelah memahami fitur-fitur baru middleware Node.js, sekarang waktunya masuk ke implementasi nyata. Bagian ini akan memberikan panduan praktis yang bisa langsung kamu terapkan di proyek BuildWithAngga atau aplikasi lainnya. Kita akan bahas best practices, keamanan, dan optimasi performa yang penting untuk dipahami.

Yang paling penting dalam implementasi middleware adalah memahami bahwa ini bukan tempat untuk logika bisnis yang berat. Middleware sebaiknya tetap ringan dan fokus pada tugas-tugas seperti autentikasi, autorisasi, routing, dan preprocessing request. Logika yang kompleks sebaiknya tetap di API routes atau server components.

Contoh Kode dan Praktek Terbaik

image.png

Mari kita mulai dengan contoh implementasi middleware yang komprehensif tapi tetap mudah dipahami. Contoh ini akan mencakup beberapa fungsi umum yang sering dibutuhkan dalam aplikasi web modern.

import { NextResponse } from 'next/server';
import { verifyAuthToken } from './lib/auth-utils';
import { logRequest } from './lib/logging';

export async function middleware(request) {
  const { pathname } = request.nextUrl;
  const response = NextResponse.next();

  try {
    // 1. Logging setiap request
    await logRequest(request);

    // 2. Tambahkan security headers
    response.headers.set('X-Frame-Options', 'DENY');
    response.headers.set('X-Content-Type-Options', 'nosniff');
    response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');

    // 3. Handle CORS untuk API routes
    if (pathname.startsWith('/api/')) {
      response.headers.set('Access-Control-Allow-Origin', process.env.ALLOWED_ORIGIN || '*');
      response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
      response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    }

    // 4. Cek autentikasi untuk protected routes
    const protectedPaths = ['/dashboard', '/admin', '/profile'];
    const needsAuth = protectedPaths.some(path => pathname.startsWith(path));

    if (needsAuth) {
      const token = request.cookies.get('auth_token')?.value;

      if (!token) {
        return NextResponse.redirect(new URL('/login', request.url));
      }

      const user = await verifyAuthToken(token);
      if (!user) {
        const loginResponse = NextResponse.redirect(new URL('/login', request.url));
        loginResponse.cookies.delete('auth_token');
        return loginResponse;
      }

      // Tambahkan user info ke headers
      response.headers.set('X-User-ID', user.id);
      response.headers.set('X-User-Role', user.role);
    }

    // 5. Redirect maintenance mode
    if (process.env.MAINTENANCE_MODE === 'true' && !pathname.startsWith('/maintenance')) {
      return NextResponse.redirect(new URL('/maintenance', request.url));
    }

    return response;

  } catch (error) {
    console.error('Middleware error:', error);
    // Jangan block request kalau ada error, tapi log untuk debugging
    return NextResponse.next();
  }
}

export const config = {
  runtime: 'nodejs',
  matcher: [
    '/((?!_next/static|_next/image|favicon.ico).*)',
  ]
};

Praktek terbaik pertama adalah selalu gunakan try-catch untuk menangani error. Middleware yang error bisa block seluruh aplikasi, jadi pastikan ada fallback yang aman. Kalau ada error, lebih baik let request continue dengan logging error untuk debugging.

Error handling yang baik juga termasuk validasi input. Selalu validasi cookies, headers, dan URL parameters sebelum digunakan. Jangan assume data yang masuk selalu dalam format yang benar.

// Helper function untuk validasi token
export async function verifyAuthToken(token) {
  try {
    if (!token || typeof token !== 'string') {
      return null;
    }

    // Decode dan verify JWT token
    const decoded = jwt.verify(token, process.env.JWT_SECRET);

    // Validasi format payload
    if (!decoded.userId || !decoded.role) {
      return null;
    }

    return {
      id: decoded.userId,
      role: decoded.role,
      email: decoded.email
    };
  } catch (error) {
    console.error('Token verification failed:', error.message);
    return null;
  }
}

// Helper function untuk logging
export async function logRequest(request) {
  try {
    const logData = {
      timestamp: new Date().toISOString(),
      method: request.method,
      url: request.url,
      userAgent: request.headers.get('user-agent')?.substring(0, 100),
      ip: request.headers.get('x-forwarded-for') || 'unknown'
    };

    // Log ke file atau external service
    console.log(JSON.stringify(logData));
  } catch (error) {
    // Silent fail untuk logging
    console.error('Logging failed:', error.message);
  }
}

Praktek terbaik lainnya adalah gunakan environment variables untuk konfigurasi yang bisa berubah. Jangan hardcode values seperti allowed origins, API keys, atau feature flags langsung di kode middleware.

Pertimbangan Keamanan Aplikasi

image.png

Keamanan adalah aspek paling kritis dalam implementasi middleware. Karena middleware berjalan di setiap request, celah keamanan di sini bisa berdampak ke seluruh aplikasi. Ada beberapa prinsip keamanan yang harus selalu diingat.

Pertama adalah prinsip least privilege. Middleware sebaiknya hanya akses data yang benar-benar dibutuhkan. Jangan ambil atau expose informasi sensitif yang tidak perlu. Misalnya, kalau cuma butuh user ID, jangan ambil data personal lengkap dari database.

import { NextResponse } from 'next/server';
import crypto from 'crypto';

export function middleware(request) {
  // Input sanitization
  const pathname = request.nextUrl.pathname;
  const sanitizedPath = pathname.replace(/[<>\\"']/g, '');

  // Rate limiting sederhana
  const clientIP = request.headers.get('x-forwarded-for') || 'unknown';
  const rateLimitKey = crypto.createHash('sha256').update(clientIP).digest('hex');

  // Validasi request method
  const allowedMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'];
  if (!allowedMethods.includes(request.method)) {
    return NextResponse.json({ error: 'Method not allowed' }, { status: 405 });
  }

  // Cek malicious patterns
  const suspiciousPatterns = [
    /\\.\\.\\//,  // Path traversal
    /<script/i, // XSS attempt
    /union.*select/i, // SQL injection
    /javascript:/i // JavaScript protocol
  ];

  const isSuspicious = suspiciousPatterns.some(pattern =>
    pattern.test(sanitizedPath) || pattern.test(request.url)
  );

  if (isSuspicious) {
    console.warn(`Suspicious request detected: ${request.url} from ${clientIP}`);
    return NextResponse.json({ error: 'Bad request' }, { status: 400 });
  }

  // Content Security Policy
  const response = NextResponse.next();
  response.headers.set('Content-Security-Policy',
    "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
  );

  return response;
}

export const config = {
  runtime: 'nodejs',
  matcher: '/((?!_next/static|_next/image|favicon.ico).*)'
};

Secret management juga sangat penting. Jangan pernah hardcode API keys, database credentials, atau secret lainnya di kode middleware. Selalu gunakan environment variables dan pastikan file .env tidak masuk ke version control.

CORS configuration harus di-setup dengan hati-hati. Jangan gunakan wildcard (*) untuk production environment. Specify domain yang diizinkan secara eksplisit. Untuk aplikasi BuildWithAngga, biasanya cukup allow domain sendiri dan beberapa subdomain yang diperlukan.

Session security juga perlu diperhatikan. Kalau pakai cookie-based authentication, pastikan cookie di-set dengan flag yang aman: httpOnly, secure (untuk HTTPS), dan sameSite. Jangan lupa set expiration time yang reasonable.

// Contoh secure cookie handling
export function setSecureAuthCookie(response, token) {
  response.cookies.set('auth_token', token, {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'strict',
    maxAge: 60 * 60 * 24 * 7, // 7 hari
    path: '/'
  });
}

Implikasi Terhadap Performa

image.png

Middleware Node.js membawa implikasi performa yang berbeda dibanding Edge Runtime. Yang paling mencolok adalah memory usage yang lebih tinggi dan cold start time yang lebih lama. Tapi di sisi lain, kita dapat akses penuh ke optimasi yang lebih canggih.

Memory management menjadi lebih penting karena Node.js Runtime tidak se-efficient Edge Runtime dalam hal resource usage. Hindari create object besar atau array yang tidak perlu di dalam middleware. Kalau harus handle data besar, pastikan ada proper cleanup.

import { NextResponse } from 'next/server';

// Cache sederhana dengan TTL
const cache = new Map();
const CACHE_TTL = 5 * 60 * 1000; // 5 menit

export async function middleware(request) {
  const cacheKey = request.url;
  const now = Date.now();

  // Cek cache terlebih dahulu
  if (cache.has(cacheKey)) {
    const { data, timestamp } = cache.get(cacheKey);

    if (now - timestamp < CACHE_TTL) {
      const response = NextResponse.next();
      response.headers.set('X-Cache', 'HIT');
      return response;
    } else {
      // Hapus cache yang expired
      cache.delete(cacheKey);
    }
  }

  // Periodic cache cleanup untuk avoid memory leak
  if (cache.size > 1000) {
    for (const [key, value] of cache.entries()) {
      if (now - value.timestamp > CACHE_TTL) {
        cache.delete(key);
      }
    }
  }

  // Process request dan cache hasilnya kalau perlu
  const response = NextResponse.next();
  response.headers.set('X-Cache', 'MISS');

  cache.set(cacheKey, {
    data: 'processed',
    timestamp: now
  });

  return response;
}

export const config = {
  runtime: 'nodejs',
  matcher: '/api/:path*'
};

Database connections perlu di-manage dengan baik. Jangan create new connection di setiap request. Gunakan connection pooling atau singleton pattern untuk reuse connections. Ini akan significantly improve performa dan reduce resource usage.

Async operations harus di-handle dengan hati-hati. Jangan block middleware execution dengan operations yang lama. Kalau ada task yang time-consuming, consider untuk di-defer ke background job atau gunakan techniques seperti fire-and-forget pattern.

Monitoring dan profiling jadi lebih mudah dengan Node.js Runtime. Kamu bisa pakai tools seperti clinic.js atau built-in Node.js profiler untuk analyze performance bottlenecks. APM tools seperti New Relic atau DataDog juga bisa provide detailed insights.

// Simple performance monitoring
export function middleware(request) {
  const startTime = Date.now();

  const response = NextResponse.next();

  // Hitung execution time
  const executionTime = Date.now() - startTime;
  response.headers.set('X-Execution-Time', `${executionTime}ms`);

  // Log slow requests
  if (executionTime > 100) {
    console.warn(`Slow middleware execution: ${executionTime}ms for ${request.url}`);
  }

  return response;
}

Caching strategy juga bisa lebih sophisticated. Dengan akses ke file system dan external services, kamu bisa implement multi-level caching atau distributed caching dengan Redis. Tapi ingat, jangan over-engineer kalau aplikasi belum butuh kompleksitas segitu.

Penutup

Middleware Node.js di Next.js 15.5 membuka peluang besar untuk implementasi server-side processing yang lebih powerful. Dengan akses penuh ke ecosystem Node.js, kamu bisa bikin autentikasi robust, operasi file system, dan integrasi database langsung di middleware tanpa batasan Edge Runtime.

Yang penting diingat adalah selalu prioritaskan keamanan dan performa. Middleware yang tidak optimal bisa impact seluruh aplikasi, jadi pastikan implement best practices dan monitor performance secara berkala.

Untuk yang mau mendalami Next.js lebih lanjut, BuildWithAngga punya kelas-kelas berkualitas dengan mentor berpengalaman dan materi yang selalu update dengan teknologi terbaru. Start dari basic dulu, lalu gradually tingkatkan kompleksitas seiring pengalaman.

Keep coding dan jangan takut eksperimen dengan fitur baru ini. Every expert dulu juga pemula - yang membedakan adalah konsistensi belajar dan praktik. Selamat coding!