fix(frontend): API client uses relative URLs in browser (same-origin) + SSR proxy route
- lib/api.ts: detect browser vs server, use relative URL in browser - /api/catalog/[...path]: proxy route to forward SSR requests to backend - .env.local: NEXT_PUBLIC_API_URL preserved for SSR - Closes FRONTEND-PROXY
This commit is contained in:
63
project/frontend/src/app/api/catalog/[...path]/route.ts
Normal file
63
project/frontend/src/app/api/catalog/[...path]/route.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
const API = process.env.NEXT_PUBLIC_API_URL ?? 'http://127.0.0.1:3000';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
return handleProxy(request);
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
return handleProxy(request);
|
||||
}
|
||||
|
||||
export async function PUT(request: NextRequest) {
|
||||
return handleProxy(request);
|
||||
}
|
||||
|
||||
export async function PATCH(request: NextRequest) {
|
||||
return handleProxy(request);
|
||||
}
|
||||
|
||||
export async function DELETE(request: NextRequest) {
|
||||
return handleProxy(request);
|
||||
}
|
||||
|
||||
async function handleProxy(request: NextRequest): Promise<NextResponse> {
|
||||
// Strip the /api/catalog prefix and forward the rest to the backend
|
||||
const path = request.nextUrl.pathname.replace('/api/catalog/', '');
|
||||
const url = `${API}/${path}${request.nextUrl.search}`;
|
||||
|
||||
try {
|
||||
const headers: Record<string, string> = {
|
||||
accept: 'application/json',
|
||||
};
|
||||
// Forward the cookie header from the incoming request (for auth)
|
||||
const cookie = request.headers.get('cookie');
|
||||
if (cookie) headers.cookie = cookie;
|
||||
|
||||
const init: RequestInit = {
|
||||
headers,
|
||||
};
|
||||
const method = request.method;
|
||||
if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) {
|
||||
init.method = method;
|
||||
init.body = await request.text();
|
||||
const contentType = request.headers.get('content-type');
|
||||
if (contentType) headers['content-type'] = contentType;
|
||||
}
|
||||
const res = await fetch(url, init);
|
||||
const data = await res.json().catch(() => ({}));
|
||||
|
||||
// Forward Set-Cookie header from backend if present
|
||||
const setCookie = res.headers.get('set-cookie');
|
||||
if (setCookie) {
|
||||
return NextResponse.json(data, { status: res.status, headers: { 'set-cookie': setCookie } });
|
||||
}
|
||||
return NextResponse.json(data, { status: res.status });
|
||||
} catch (err) {
|
||||
return NextResponse.json(
|
||||
{ error: { code: 'PROXY_ERROR', message: String(err) } },
|
||||
{ status: 502 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,17 @@ interface CmsPage {
|
||||
status: string;
|
||||
}
|
||||
|
||||
// Use env var if set, otherwise use relative URL (works for SSR and client-side via same-origin or CORS)
|
||||
const BASE = process.env.NEXT_PUBLIC_API_URL || '';
|
||||
// Use env var if set, otherwise proxy through /api/catalog
|
||||
// In SSR: NEXT_PUBLIC_API_URL is http://127.0.0.1:3000 (backend direct)
|
||||
// In prod (relative mode): empty → /api/catalog (same-origin proxy)
|
||||
const BASE = (() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
// Browser: use same-origin relative URL (works from any network)
|
||||
return '';
|
||||
}
|
||||
// Server/SSR: use backend URL from env
|
||||
return process.env.NEXT_PUBLIC_API_URL ?? 'http://127.0.0.1:3000';
|
||||
})();
|
||||
|
||||
export async function fetchCategories(): Promise<Category[]> {
|
||||
const res = await fetch(`${BASE}/categories/tree`);
|
||||
|
||||
Reference in New Issue
Block a user