/** * API Configuration * Centralized configuration for API endpoints * * In Development: Calls backend directly at http://localhost:8000 * In Production: Uses Next.js rewrites/nginx proxy at /api (internal network only) */ // Production: Use relative URLs (nginx proxy) for client-side // For server-side (SSR), use internal backend URL or public domain const isProduction = process.env.NODE_ENV === 'production'; // Detect if we're on the server (Node.js) or client (browser) const isServer = typeof window === 'undefined'; // For server-side rendering, we need an absolute URL // During build time, use internal backend URL directly (faster, no SSL issues) // At runtime, use public domain (goes through nginx which adds API key header) const getServerApiUrl = () => { if (isProduction) { // Check if we're in build context (no access to window, and NEXT_PHASE might be set) // During build, use internal backend URL directly // At runtime (SSR), use public domain through nginx const isBuildTime = process.env.NEXT_PHASE === 'phase-production-build' || !process.env.NEXT_RUNTIME; if (isBuildTime) { // Build time: use internal backend URL directly return process.env.INTERNAL_API_URL || 'http://127.0.0.1:1086'; } else { // Runtime SSR: use public domain - nginx will proxy and add API key header return process.env.NEXT_PUBLIC_SITE_URL || 'https://gnxsoft.com'; } } return process.env.NEXT_PUBLIC_API_URL || 'http://127.0.0.1:1086'; }; // For client-side, use relative URLs in production (proxied by nginx) // For server-side, use absolute URLs export const API_BASE_URL = isServer ? getServerApiUrl() // Server-side: absolute URL : (isProduction ? '' // Client-side production: relative URLs (proxied by nginx) : (process.env.NEXT_PUBLIC_API_URL || 'http://127.0.0.1:1086')); // Development: direct backend // Internal API key for server-side requests (must match backend INTERNAL_API_KEY) // This is only used when calling backend directly (build time or internal requests) // SECURITY: Never hardcode API keys in production - always use environment variables const getInternalApiKey = (): string => { const apiKey = process.env.INTERNAL_API_KEY; // Check if we're in build phase (Next.js build context) // During build, NEXT_RUNTIME is typically not set // Also check for specific build phases const isBuildTime = !process.env.NEXT_RUNTIME || // Most reliable indicator - not set during build process.env.NEXT_PHASE === 'phase-production-build' || process.env.NEXT_PHASE === 'phase-production-compile' || process.env.NEXT_PHASE === 'phase-development-build'; if (!apiKey) { // During build time, be lenient - allow build to proceed // The key will be validated when actually used (in getApiHeaders) if (isBuildTime) { // Build time: allow fallback (will be validated when actually used) return 'build-time-fallback-key'; } // Runtime production: require the key (but only validate when actually used) if (isProduction) { // Don't throw here - validate lazily in getApiHeaders when actually needed // This allows the build to complete even if key is missing return 'runtime-requires-key'; } // Development fallback (only for local development) return 'dev-key-not-for-production'; } return apiKey; }; // Lazy getter - only evaluates when accessed let _internalApiKey: string | null = null; export const getInternalApiKeyLazy = (): string => { if (_internalApiKey === null) { _internalApiKey = getInternalApiKey(); } return _internalApiKey; }; // For backward compatibility - evaluates at module load but uses lenient validation export const INTERNAL_API_KEY = getInternalApiKeyLazy(); // Helper to get headers for API requests // Adds API key header when calling internal backend directly export const getApiHeaders = (): Record => { const headers: Record = { 'Content-Type': 'application/json', }; // If we're calling the internal backend directly (not through nginx), // add the API key header (lazy evaluation - only when actually needed) if (isServer && API_BASE_URL.includes('127.0.0.1:1086')) { const apiKey = getInternalApiKeyLazy(); // Validate API key when actually used (not at module load time) if (apiKey === 'runtime-requires-key' && isProduction) { const actualKey = process.env.INTERNAL_API_KEY; if (!actualKey) { throw new Error( 'INTERNAL_API_KEY environment variable is required in production runtime. ' + 'Set it in your .env.production file or environment variables.' ); } // Update cached value _internalApiKey = actualKey; headers['X-Internal-API-Key'] = actualKey; } else { headers['X-Internal-API-Key'] = apiKey; } } return headers; }; export const API_CONFIG = { // Django API Base URL BASE_URL: API_BASE_URL, // Media files URL (for uploaded images, etc.) MEDIA_URL: `${API_BASE_URL}/media`, // API Endpoints ENDPOINTS: { CONTACT: '/api/contact', CONTACT_SUBMISSIONS: '/api/contact/submissions', CONTACT_STATS: '/api/contact/submissions/stats', CONTACT_RECENT: '/api/contact/submissions/recent', SERVICES: '/api/services', SERVICES_FEATURED: '/api/services/featured', SERVICES_SEARCH: '/api/services/search', SERVICES_STATS: '/api/services/stats', SERVICES_CATEGORIES: '/api/services/categories', }, // Request timeout (in milliseconds) TIMEOUT: 10000, // Retry configuration RETRY: { ATTEMPTS: 3, DELAY: 1000, } } as const; export default API_CONFIG;