const TRAILING_SLASH = /\/$/; export function getBackendBaseUrl(): string { const raw = process.env.BACKEND_URL ?? process.env.NEXT_PUBLIC_BACKEND_URL; if (!raw) { throw new Error("BACKEND_URL 未配置"); } return raw.replace(TRAILING_SLASH, ""); } export function tryGetBackendBaseUrl(): string | null { try { return getBackendBaseUrl(); } catch { return null; } } type SearchParamsInit = Record | URLSearchParams; export function buildBackendUrl(path: string, params?: SearchParamsInit): URL { const base = getBackendBaseUrl(); const url = new URL(path, base); if (params) { const search = params instanceof URLSearchParams ? params : new URLSearchParams( Object.entries(params).reduce>((acc, [key, value]) => { if (value !== undefined) { acc[key] = String(value); } return acc; }, {}), ); search.forEach((value, key) => { if (value !== undefined && value !== null) { url.searchParams.set(key, value); } }); } return url; } export async function fetchBackend(path: string, init?: RequestInit, params?: SearchParamsInit) { const url = buildBackendUrl(path, params); return fetch(url, init); }