Make API URL dynamic based on hostname
Some checks failed
CI / Frontend Lint & Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Backend Lint (push) Has been cancelled
CI / Backend Tests (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
Deploy / Build & Push Images (push) Has been cancelled
Deploy / Deploy to Server (push) Has been cancelled
Deploy / Notify (push) Has been cancelled

- 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)
This commit is contained in:
2025-12-09 08:09:46 +01:00
parent ca5f2739db
commit db8cc399ef

View File

@ -1,20 +1,44 @@
/** /**
* API client for pounce backend * 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 = (): string => {
const getApiBase = () => { // Server-side rendering: use environment variable or localhost
const baseUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000' if (typeof window === 'undefined') {
// Remove trailing slash if present const baseUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api/v1'
const cleanBase = baseUrl.replace(/\/$/, '') return baseUrl.replace(/\/$/, '')
// Add /api/v1 if not present
if (cleanBase.endsWith('/api/v1')) {
return cleanBase
} }
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 { interface ApiError {
detail: string detail: string
@ -44,7 +68,7 @@ class ApiClient {
endpoint: string, endpoint: string,
options: RequestInit = {} options: RequestInit = {}
): Promise<T> { ): Promise<T> {
const url = `${API_BASE}${endpoint}` const url = `${getApiBaseUrl()}${endpoint}`
const headers: Record<string, string> = { const headers: Record<string, string> = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
...options.headers as Record<string, string>, ...options.headers as Record<string, string>,