Merge pull request #69 from christianlouis/copilot/fix-cors-policy-error

fix: replace build-time NEXT_PUBLIC_API_URL with runtime proxy for backend API calls
This commit is contained in:
Christian Krakau-Louis
2026-03-24 02:23:23 +01:00
committed by GitHub
5 changed files with 47 additions and 5 deletions
+1
View File
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Fixed
- Fixed frontend API calls being hardcoded to `http://localhost:8000` in production: `NEXT_PUBLIC_API_URL` is baked into the JavaScript bundle at Next.js build time, so it can never be overridden at container runtime. Replaced the `NEXT_PUBLIC_API_URL` mechanism with a Next.js Route Handler proxy at `/api/v1/[...path]` that reads `process.env.BACKEND_URL` at server startup and proxies all `/api/v1/*` requests to the real backend. The frontend Axios client now uses a relative base URL (`/api/v1`), which also eliminates the CORS issue since the browser only ever talks to the same-origin Next.js server. Update `BACKEND_URL=http://backend:8000` in `docker-compose.new.yml` (or your deployment env) to point the proxy at your backend.
- Fixed infinite spinning wheel on the home page: `authStore` no longer initialises `isLoading` as `true` unconditionally — it is now `false` when no access token exists in `localStorage`, so unauthenticated users see the landing page immediately instead of an endless spinner
- Home page now performs an auth check when a token is present in `localStorage`, redirecting authenticated users to the dashboard and clearing stale tokens on failure
- Wrapped `useSearchParams()` in a `Suspense` boundary in `frontend/src/app/auth/callback/page.tsx` to fix the Next.js build error: "useSearchParams() should be wrapped in a suspense boundary at page /auth/callback"
+1 -1
View File
@@ -96,7 +96,7 @@ services:
ports:
- "3000:3000"
environment:
- NEXT_PUBLIC_API_URL=http://backend:8000
- BACKEND_URL=http://backend:8000
depends_on:
- backend
restart: unless-stopped
+1
View File
@@ -98,6 +98,7 @@ Comprehensive task breakdown for repository improvements and production readines
- [x] Remove CodeQL checks from CI (was blocking builds)
- [x] Upgrade SQLAlchemy to 2.0.48 to fix Python 3.14 test failures
- [x] Fix Docker build failure: wrap `useSearchParams()` in Suspense boundary in `/auth/callback` page
- [x] Fix frontend API URL hardcoded to `localhost:8000` in production: replaced build-time `NEXT_PUBLIC_API_URL` with a runtime Next.js Route Handler proxy (`/api/v1/[...path]`) reading `BACKEND_URL` at server startup
### In Progress 🔨
- [ ] Configure branch protection rules
@@ -0,0 +1,43 @@
import { NextRequest, NextResponse } from "next/server";
const BACKEND_URL = process.env.BACKEND_URL ?? "http://localhost:8000";
type RouteContext = { params: Promise<{ path: string[] }> };
async function handler(request: NextRequest, context: RouteContext) {
const { path } = await context.params;
const targetUrl = `${BACKEND_URL}/api/v1/${path.join("/")}${request.nextUrl.search}`;
const headers = new Headers(request.headers);
headers.delete("host");
// Request uncompressed response so the body can be forwarded as-is
headers.set("accept-encoding", "identity");
const hasBody = request.method !== "GET" && request.method !== "HEAD";
const body = hasBody ? await request.arrayBuffer() : undefined;
try {
const upstream = await fetch(targetUrl, {
method: request.method,
headers,
body,
});
return new NextResponse(upstream.body, {
status: upstream.status,
statusText: upstream.statusText,
headers: upstream.headers,
});
} catch (error) {
console.error("Proxy error forwarding to backend:", error);
return NextResponse.json({ detail: "Backend unavailable" }, { status: 502 });
}
}
export const GET = handler;
export const POST = handler;
export const PUT = handler;
export const PATCH = handler;
export const DELETE = handler;
export const HEAD = handler;
export const OPTIONS = handler;
+1 -4
View File
@@ -1,10 +1,7 @@
import axios from "axios";
const API_BASE_URL =
process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";
const api = axios.create({
baseURL: `${API_BASE_URL}/api/v1`,
baseURL: `/api/v1`,
headers: {
"Content-Type": "application/json",
},