refactor(env): update NEXT_PUBLIC_API_URL to use 'auto' for better flexibility
This commit is contained in:
@@ -12,6 +12,7 @@ POSTGRES_PASSWORD=postgres
|
||||
POSTGRES_PORT=5432
|
||||
|
||||
# --- backend settings (see backend/.env.example for full list) ---
|
||||
# For remote access, set this to your UI origin (e.g. http://<server-ip>:3000 or https://mc.example.com).
|
||||
CORS_ORIGINS=http://localhost:3000
|
||||
DB_AUTO_MIGRATE=true
|
||||
LOG_LEVEL=INFO
|
||||
@@ -22,6 +23,6 @@ LOCAL_AUTH_TOKEN=
|
||||
|
||||
# --- frontend settings ---
|
||||
# REQUIRED: Public URL used by the browser to reach the API.
|
||||
# If this is missing/blank, frontend API calls (e.g. Activity feed) will break.
|
||||
# Example (local dev / compose on your machine):
|
||||
NEXT_PUBLIC_API_URL=http://localhost:8000
|
||||
# Use `auto` to target the same host currently serving Mission Control on port 8000.
|
||||
# Example (explicit override): NEXT_PUBLIC_API_URL=https://mc.example.com
|
||||
NEXT_PUBLIC_API_URL=auto
|
||||
|
||||
@@ -91,7 +91,8 @@ cp .env.example .env
|
||||
Before startup:
|
||||
|
||||
- Set `LOCAL_AUTH_TOKEN` to a non-placeholder value (minimum 50 characters) when `AUTH_MODE=local`.
|
||||
- Ensure `NEXT_PUBLIC_API_URL` is reachable from your browser.
|
||||
- `NEXT_PUBLIC_API_URL=auto` (default) resolves to `http(s)://<current-host>:8000`.
|
||||
- Set an explicit URL when your API is behind a reverse proxy or non-default port.
|
||||
|
||||
### 2. Start Mission Control
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ LOG_USE_UTC=false
|
||||
REQUEST_LOG_SLOW_MS=1000
|
||||
REQUEST_LOG_INCLUDE_HEALTH=false
|
||||
DATABASE_URL=postgresql+psycopg://postgres:postgres@localhost:5432/mission_control
|
||||
# For remote access, set this to your UI origin (e.g. http://<server-ip>:3000 or https://mc.example.com).
|
||||
CORS_ORIGINS=http://localhost:3000
|
||||
BASE_URL=
|
||||
# Security response headers (blank values disable each header).
|
||||
|
||||
@@ -55,7 +55,7 @@ services:
|
||||
build:
|
||||
context: ./frontend
|
||||
args:
|
||||
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:8000}
|
||||
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-auto}
|
||||
NEXT_PUBLIC_AUTH_MODE: ${AUTH_MODE}
|
||||
# Optional, user-managed env file.
|
||||
# IMPORTANT: do NOT load `.env.example` here because it contains non-empty
|
||||
@@ -64,7 +64,7 @@ services:
|
||||
- path: ./frontend/.env
|
||||
required: false
|
||||
environment:
|
||||
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:8000}
|
||||
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-auto}
|
||||
NEXT_PUBLIC_AUTH_MODE: ${AUTH_MODE}
|
||||
depends_on:
|
||||
- backend
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# REQUIRED: base URL for frontend -> backend calls (must be set for Activity feed and other API calls).
|
||||
# Must be reachable from the browser (host).
|
||||
NEXT_PUBLIC_API_URL=http://localhost:8000
|
||||
# Base URL for frontend -> backend calls.
|
||||
# Use `auto` to target the same host currently serving Mission Control on port 8000.
|
||||
# Example explicit override: https://mc.example.com
|
||||
NEXT_PUBLIC_API_URL=auto
|
||||
|
||||
# Auth mode: clerk or local.
|
||||
# - clerk: Clerk sign-in flow
|
||||
|
||||
@@ -13,7 +13,7 @@ COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . ./
|
||||
|
||||
# Allows configuring the API URL at build time.
|
||||
ARG NEXT_PUBLIC_API_URL=http://localhost:8000
|
||||
ARG NEXT_PUBLIC_API_URL=auto
|
||||
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
||||
ARG NEXT_PUBLIC_AUTH_MODE
|
||||
ENV NEXT_PUBLIC_AUTH_MODE=${NEXT_PUBLIC_AUTH_MODE}
|
||||
@@ -28,7 +28,7 @@ ARG NEXT_PUBLIC_AUTH_MODE
|
||||
|
||||
# If provided at runtime, Next will expose NEXT_PUBLIC_* to the browser as well
|
||||
# (but note some values may be baked at build time).
|
||||
ENV NEXT_PUBLIC_API_URL=http://localhost:8000
|
||||
ENV NEXT_PUBLIC_API_URL=auto
|
||||
ENV NEXT_PUBLIC_AUTH_MODE=${NEXT_PUBLIC_AUTH_MODE}
|
||||
|
||||
COPY --from=builder /app/.next ./.next
|
||||
|
||||
@@ -44,15 +44,15 @@ The frontend reads configuration from standard Next.js env files (`.env.local`,
|
||||
|
||||
#### `NEXT_PUBLIC_API_URL`
|
||||
|
||||
Base URL of the backend API.
|
||||
Base URL of the backend API (or `auto`).
|
||||
|
||||
- Default for local dev: `http://localhost:8000`
|
||||
- Default: `auto` (resolved in browser as `http(s)://<current-host>:8000`)
|
||||
- Used by the generated API client and helpers (see `src/lib/api-base.ts` and `src/api/mutator.ts`).
|
||||
|
||||
Example:
|
||||
|
||||
```env
|
||||
NEXT_PUBLIC_API_URL=http://localhost:8000
|
||||
NEXT_PUBLIC_API_URL=auto
|
||||
```
|
||||
|
||||
### Authentication mode
|
||||
@@ -141,9 +141,10 @@ If you’re working on self-hosting, prefer running compose from the repo root s
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### `NEXT_PUBLIC_API_URL is not set`
|
||||
### `NEXT_PUBLIC_API_URL` and remote hosts
|
||||
|
||||
The API client throws if `NEXT_PUBLIC_API_URL` is missing.
|
||||
If unset or set to `auto`, the client uses `http(s)://<current-host>:8000`.
|
||||
If your backend is on a different host/port, set `NEXT_PUBLIC_API_URL` explicitly.
|
||||
|
||||
Fix:
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getLocalAuthToken, isLocalAuthMode } from "@/auth/localAuth";
|
||||
import { getApiBaseUrl } from "@/lib/api-base";
|
||||
|
||||
type ClerkSession = {
|
||||
getToken: () => Promise<string>;
|
||||
@@ -39,11 +40,7 @@ export const customFetch = async <T>(
|
||||
url: string,
|
||||
options: RequestInit,
|
||||
): Promise<T> => {
|
||||
const rawBaseUrl = process.env.NEXT_PUBLIC_API_URL;
|
||||
if (!rawBaseUrl) {
|
||||
throw new Error("NEXT_PUBLIC_API_URL is not set.");
|
||||
}
|
||||
const baseUrl = rawBaseUrl.replace(/\/+$/, "");
|
||||
const baseUrl = getApiBaseUrl();
|
||||
|
||||
const headers = new Headers(options.headers);
|
||||
const hasBody = options.body !== undefined && options.body !== null;
|
||||
|
||||
@@ -7,17 +7,18 @@ import { setLocalAuthToken } from "@/auth/localAuth";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { getApiBaseUrl } from "@/lib/api-base";
|
||||
|
||||
const LOCAL_AUTH_TOKEN_MIN_LENGTH = 50;
|
||||
|
||||
async function validateLocalToken(token: string): Promise<string | null> {
|
||||
const rawBaseUrl = process.env.NEXT_PUBLIC_API_URL;
|
||||
if (!rawBaseUrl) {
|
||||
return "NEXT_PUBLIC_API_URL is not set.";
|
||||
let baseUrl: string;
|
||||
try {
|
||||
baseUrl = getApiBaseUrl();
|
||||
} catch {
|
||||
return "Unable to resolve backend URL.";
|
||||
}
|
||||
|
||||
const baseUrl = rawBaseUrl.replace(/\/+$/, "");
|
||||
|
||||
let response: Response;
|
||||
try {
|
||||
response = await fetch(`${baseUrl}/api/v1/users/me`, {
|
||||
|
||||
27
frontend/src/lib/api-base.test.ts
Normal file
27
frontend/src/lib/api-base.test.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { getApiBaseUrl } from "./api-base";
|
||||
|
||||
describe("getApiBaseUrl", () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it("returns normalized explicit URL", () => {
|
||||
vi.stubEnv("NEXT_PUBLIC_API_URL", "https://api.example.com///");
|
||||
|
||||
expect(getApiBaseUrl()).toBe("https://api.example.com");
|
||||
});
|
||||
|
||||
it("auto-resolves from browser host when set to auto", () => {
|
||||
vi.stubEnv("NEXT_PUBLIC_API_URL", "auto");
|
||||
|
||||
expect(getApiBaseUrl()).toBe("http://localhost:8000");
|
||||
});
|
||||
|
||||
it("auto-resolves from browser host when unset", () => {
|
||||
vi.stubEnv("NEXT_PUBLIC_API_URL", "");
|
||||
|
||||
expect(getApiBaseUrl()).toBe("http://localhost:8000");
|
||||
});
|
||||
});
|
||||
@@ -1,11 +1,22 @@
|
||||
export function getApiBaseUrl(): string {
|
||||
const raw = process.env.NEXT_PUBLIC_API_URL;
|
||||
if (!raw) {
|
||||
throw new Error("NEXT_PUBLIC_API_URL is not set.");
|
||||
}
|
||||
const raw = process.env.NEXT_PUBLIC_API_URL?.trim();
|
||||
if (raw && raw.toLowerCase() !== "auto") {
|
||||
const normalized = raw.replace(/\/+$/, "");
|
||||
if (!normalized) {
|
||||
throw new Error("NEXT_PUBLIC_API_URL is invalid.");
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
const protocol = window.location.protocol === "https:" ? "https" : "http";
|
||||
const host = window.location.hostname;
|
||||
if (host) {
|
||||
return `${protocol}://${host}:8000`;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
"NEXT_PUBLIC_API_URL is not set and cannot be auto-resolved outside the browser.",
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user