From db8cc399ef08760a0279c65d141bf79d77f4bf18 Mon Sep 17 00:00:00 2001 From: Yves Gugger Date: Tue, 9 Dec 2025 08:09:46 +0100 Subject: [PATCH] Make API URL dynamic based on hostname MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - localhost/127.0.0.1 → http://localhost:8000/api/v1 - Local network IPs → http://{hostname}:8000/api/v1 - Production domains → https://{hostname}/api/v1 (requires reverse proxy) --- frontend/src/lib/api.ts | 46 +++++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index af4b4dd..b4a071f 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -1,20 +1,44 @@ /** * API client for pounce backend + * + * API URL is determined dynamically based on the current hostname: + * - localhost/127.0.0.1 → http://localhost:8000/api/v1 + * - Local network IPs (10.x, 192.168.x) → http://{hostname}:8000/api/v1 + * - Production (any other domain) → https://{hostname}/api/v1 (requires reverse proxy) */ -// Ensure API_BASE ends with /api/v1 and no trailing slash -const getApiBase = () => { - const baseUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000' - // Remove trailing slash if present - const cleanBase = baseUrl.replace(/\/$/, '') - // Add /api/v1 if not present - if (cleanBase.endsWith('/api/v1')) { - return cleanBase +const getApiBase = (): string => { + // Server-side rendering: use environment variable or localhost + if (typeof window === 'undefined') { + const baseUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api/v1' + return baseUrl.replace(/\/$/, '') } - return `${cleanBase}/api/v1` + + const { protocol, hostname, port } = window.location + + // Localhost development + if (hostname === 'localhost' || hostname === '127.0.0.1') { + return 'http://localhost:8000/api/v1' + } + + // Local network (10.x.x.x, 192.168.x.x, 172.16-31.x.x) + if (/^(10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.)/.test(hostname)) { + return `http://${hostname}:8000/api/v1` + } + + // Production: use same protocol and domain with /api/v1 path + // This requires a reverse proxy (nginx/caddy) to route /api/v1 to the backend + return `${protocol}//${hostname}/api/v1` } -const API_BASE = getApiBase() +// Lazy-evaluated to ensure window is available on client +let _apiBase: string | null = null +const getApiBaseUrl = (): string => { + if (_apiBase === null) { + _apiBase = getApiBase() + } + return _apiBase +} interface ApiError { detail: string @@ -44,7 +68,7 @@ class ApiClient { endpoint: string, options: RequestInit = {} ): Promise { - const url = `${API_BASE}${endpoint}` + const url = `${getApiBaseUrl()}${endpoint}` const headers: Record = { 'Content-Type': 'application/json', ...options.headers as Record,