15. Proxy & API Routing di Next.js (Built-in)

Berbeda dengan Vite yang membutuhkan konfigurasi vite.config.ts devProxy atau server Express terpisah, Next.js memiliki dua mekanisme proxy bawaan yang tidak memerlukan package tambahan:

A. rewrites di next.config.ts — HTTP Proxy ke Backend

Digunakan untuk mem-proxy request dari frontend ke backend API, sehingga menghilangkan masalah CORS dan menyembunyikan URL backend asli dari browser.

// next.config.ts
const nextConfig = {
  async rewrites() {
    return [
      {
        source: "/api/:path*",
        destination: `${process.env.BACKEND_URL}/api/:path*`,
      },
    ];
  },
};

export default nextConfig;

Keuntungan rewrites:

  • ✅ Request fetch("/api/users") di browser tidak pernah tahu URL backend asli
  • ✅ Mengatasi CORS di development & production tanpa konfigurasi tambahan
  • ✅ Tidak perlu package http-proxy-middleware atau server terpisah
  • ✅ Berjalan di server layer (SSR-safe)

B. proxy.js — Auth Guard & Role-Based Access Control (Next.js)

Berjalan di Edge Runtime sebelum request mencapai halaman. Digunakan untuk:

  • Redirect user tidak terautentikasi ke halaman login
  • Role-based access control (RBAC) berdasarkan cookie
  • Menambahkan header kustom sebelum request diteruskan
// proxy.js (di ROOT project, bukan dalam src/)
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

const ROLE_PERMISSIONS: Record<string, string[]> = {
  ADMIN: ["/dashboard", "/users", "/settings"],
  USER: ["/dashboard", "/profile"],
};

export function proxy(request: NextRequest) {
  const { pathname } = request.nextUrl;

  // Bypass untuk static assets & public routes
  if (
    pathname.startsWith("/_next") ||
    pathname.startsWith("/api") ||
    pathname === "/login"
  ) {
    return NextResponse.next();
  }

  const accessToken = request.cookies.get("accessToken")?.value;
  const roleCode = request.cookies.get("roleCode")?.value;

  // Redirect ke login jika tidak ada token
  if (!accessToken) {
    return NextResponse.redirect(new URL("/login", request.url));
  }

  // Cek permission berdasarkan role
  const allowedPaths = ROLE_PERMISSIONS[roleCode ?? ""] ?? [];
  const isAllowed = allowedPaths.some(
    (path) => pathname === path || pathname.startsWith(path + "/"),
  );

  if (!isAllowed) {
    return NextResponse.redirect(new URL("/dashboard", request.url));
  }

  return NextResponse.next();
}

// Definisikan route mana yang dijalankan proxy.js
export const config = {
  matcher: [
    "/((?!api|_next/static|_next/image|favicon.ico|.*\.(?:png|jpg|svg)$).*)",
  ],
};

C. vite.config.ts — Dev Proxy (Vite)

Untuk development, Vite memiliki proxy bawaan di vite.config.ts. Nama file tetap vite.config.ts — defineConfig adalah nama function yang diimport dari package vite untuk memberikan type-safety pada config.

server.proxy bekerja sama seperti rewrites di Next.js: browser hanya memanggil /api/*, URL backend asli tidak pernah terekspos ke browser. Perbedaannya, ini hanya aktif saat npm run dev — di production, Nginx yang menggantikan peran ini.

// vite.config.ts
import { defineConfig } from "vite"; // defineConfig = function dari package vite
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      // Request browser ke /api/* → diteruskan ke VITE_BACKEND_URL/api/*
      // Browser tidak pernah tahu URL backend asli ✅ (sama seperti Next.js rewrites)
      "/api": {
        target: process.env.VITE_BACKEND_URL || "http://localhost:8080",
        changeOrigin: true, // Ubah header Host sesuai target
        secure: false, // Izinkan self-signed SSL di dev
      },
    },
  },
});

Perbandingan dengan Next.js: Next.js rewrites aktif di dev & production. Vite server.proxy hanya aktif di dev. Di production Vite, URL backend disembunyikan oleh Nginx proxy_pass.

D. Production Deployment Vite SPA

Berbeda dengan Next.js yang berjalan sebagai Node.js server, Vite SPA menghasilkan file statis murni (dist/) yang tidak membutuhkan runtime server Node.js. Di production, file tersebut cukup disajikan oleh Nginx atau CDN.

Tidak ada proxy.js custom di production Vite. RBAC dan Auth Guard sepenuhnya ditangani oleh PrivateRoute.tsx (client) + respons 401/403 dari backend API.

Opsi Deployment (pilih salah satu):

PlatformCara KerjaCORS/Proxy
Vercel / NetlifyUpload dist/, otomatis via CDNConfig vercel.json / _redirects
Nginx (VPS)Nginx sajikan dist/ sebagai static filesNginx proxy_pass ke backend
Docker + NginxMulti-stage build: Node build → Nginx serveNginx proxy_pass ke backend
S3 + CloudFrontUpload ke S3, CloudFront sebagai CDNCloudFront Origin + CORS header

Contoh Nginx Config (Vite SPA):

# /etc/nginx/sites-available/my-app
server {
    listen 80;
    server_name my-app.com;

    root /var/www/my-app/dist;
    index index.html;

    # SPA fallback — semua route diarahkan ke index.html
    location / {
        try_files $uri $uri/ /index.html;
    }

    # Proxy /api/* ke backend (menghilangkan CORS di production)
    location /api/ {
        proxy_pass http://localhost:8080/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Kesimpulan: Di Vite, vite.config.ts proxy hanya aktif saat development. Di production, Nginx atau CDN yang mengurus routing dan proxy API. Auth guard dan RBAC sepenuhnya di tangan PrivateRoute.tsx dan respons backend.

E. PrivateRoute.tsx — Client-Side Auth Guard + RBAC (Vite)

Selain proxy.js di server, Vite juga membutuhkan RBAC di sisi React. Ini adalah lapis kedua pertahanan: mencegah komponen ter-render sebelum status sesi dan role diverifikasi di browser.

Sumber role: Data role dibaca dari sesi Better Auth (useSession) yang sudah ditetapkan server saat login. Cookie roleCode dipakai server (proxy.js), sedangkan session.user.role dipakai client (PrivateRoute). Keduanya harus sinkron.

// routes/PrivateRoute.tsx
import { Navigate, useLocation } from "react-router-dom";
import { useSession } from "@/lib/auth-client"; // Better Auth

interface PrivateRouteProps {
  children: React.ReactNode;
  allowedRoles?: string[]; // Sama dengan ROLE_PERMISSIONS di proxy.js
}

export function PrivateRoute({ children, allowedRoles }: PrivateRouteProps) {
  const { data: session, isPending } = useSession();
  const location = useLocation();

  // Menunggu verifikasi sesi dari server
  if (isPending) {
    return <div>Memverifikasi sesi...</div>;
  }

  // Tidak ada sesi → redirect ke login
  if (!session?.user) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  // RBAC: role tidak diizinkan → redirect ke dashboard
  if (allowedRoles && !allowedRoles.includes(session.user.role)) {
    return <Navigate to="/dashboard" replace />;
  }

  return <>{children}</>;
}
// routes/index.tsx — contoh RBAC per route
import { createBrowserRouter } from "react-router-dom";
import { PrivateRoute } from "./PrivateRoute";

export const router = createBrowserRouter([
  { path: "/login", element: <Login /> },
  {
    // Semua role terautentikasi boleh masuk
    path: "/dashboard",
    element: (
      <PrivateRoute allowedRoles={["ADMIN", "USER"]}>
        <Dashboard />
      </PrivateRoute>
    ),
  },
  {
    // Hanya ADMIN
    path: "/users",
    element: (
      <PrivateRoute allowedRoles={["ADMIN"]}>
        <UserManagement />
      </PrivateRoute>
    ),
  },
  {
    // Hanya ADMIN
    path: "/settings",
    element: (
      <PrivateRoute allowedRoles={["ADMIN"]}>
        <Settings />
      </PrivateRoute>
    ),
  },
]);

Catatan: proxy.js Express (server) dan PrivateRoute.tsx (client) wajib konsisten dalam daftar role dan path yang diizinkan. proxy.js adalah lapis pertama (blokir sebelum HTML dikirim), PrivateRoute.tsx adalah lapis kedua (blokir sebelum komponen di-render).

Perbandingan: Next.js vs Vite untuk Auth Guard & RBAC

KebutuhanNext.jsVite
Auth Guard (Prod)proxy.js (Edge Runtime)PrivateRoute.tsx + respons 401/403 dari API
RBACproxy.js (server-side)PrivateRoute.tsx (client-side)

Aturan: proxy.js hanya ada di Next.js (root project, sejajar package.json). Vite tidak memiliki proxy.js — production-nya adalah file statis yang disajikan Nginx atau CDN.

© 2026 Bit Group Asia. All rights reserved.

ArchitectureSecurityGuidelines