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-middlewareatau 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
rewritesaktif di dev & production. Viteserver.proxyhanya aktif di dev. Di production Vite, URL backend disembunyikan oleh Nginxproxy_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.jscustom di production Vite. RBAC dan Auth Guard sepenuhnya ditangani olehPrivateRoute.tsx(client) + respons 401/403 dari backend API.
Opsi Deployment (pilih salah satu):
| Platform | Cara Kerja | CORS/Proxy |
|---|---|---|
| Vercel / Netlify | Upload dist/, otomatis via CDN | Config vercel.json / _redirects |
| Nginx (VPS) | Nginx sajikan dist/ sebagai static files | Nginx proxy_pass ke backend |
| Docker + Nginx | Multi-stage build: Node build → Nginx serve | Nginx proxy_pass ke backend |
| S3 + CloudFront | Upload ke S3, CloudFront sebagai CDN | CloudFront 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.tsproxy hanya aktif saat development. Di production, Nginx atau CDN yang mengurus routing dan proxy API. Auth guard dan RBAC sepenuhnya di tanganPrivateRoute.tsxdan 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. CookieroleCodedipakai server (proxy.js), sedangkansession.user.roledipakai 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.jsExpress (server) danPrivateRoute.tsx(client) wajib konsisten dalam daftar role dan path yang diizinkan.proxy.jsadalah lapis pertama (blokir sebelum HTML dikirim),PrivateRoute.tsxadalah lapis kedua (blokir sebelum komponen di-render).
Perbandingan: Next.js vs Vite untuk Auth Guard & RBAC
| Kebutuhan | Next.js | Vite |
|---|---|---|
| Auth Guard (Prod) | proxy.js (Edge Runtime) | PrivateRoute.tsx + respons 401/403 dari API |
| RBAC | proxy.js (server-side) | PrivateRoute.tsx (client-side) |
Aturan:
proxy.jshanya ada di Next.js (root project, sejajarpackage.json). Vite tidak memilikiproxy.js— production-nya adalah file statis yang disajikan Nginx atau CDN.