fix(web): SSR calls use cluster-internal backend URL to bypass nginx timeout

All serverFetch calls were going to https://api.marktvogt.de (public
gateway), creating a second nginx hop for every SSR operation. Slow LLM
calls (merge-plan, research-plan) hit the 60s proxy_read_timeout.

- Add PRIVATE_API_BASE_URL=http://marktvogt-backend to web Helm config
- serverFetch now builds SERVER_API_BASE from PRIVATE_API_BASE_URL at
  runtime (falls back to PUBLIC_API_BASE_URL when not set)
- apiFetch accepts optional baseURL param; client-side calls unchanged
This commit is contained in:
2026-04-25 22:25:31 +02:00
parent 4916b0d6af
commit caaad8adf4
3 changed files with 13 additions and 3 deletions

View File

@@ -64,6 +64,8 @@ config:
HOST: "0.0.0.0"
# Cloudflare Turnstile — read at runtime via $env/dynamic/public
PUBLIC_TURNSTILE_SITE_KEY: "0x4AAAAAACjLCV-78Ql1oTPz"
# Cluster-internal backend URL — SSR calls bypass the public gateway entirely
PRIVATE_API_BASE_URL: "http://marktvogt-backend"
nodeSelector: {}
tolerations: []

View File

@@ -1,8 +1,14 @@
import type { Cookies } from '@sveltejs/kit';
import { env } from '$env/dynamic/private';
import { PUBLIC_API_BASE_URL } from '$env/static/public';
import { apiFetch } from './client.js';
import type { ApiResponse, AuthData } from './types.js';
import { setAuthCookies } from '$lib/auth/cookies.js';
// Cluster-internal URL bypasses the public gateway (no nginx hop, no 60s timeout).
// Falls back to PUBLIC_API_BASE_URL if PRIVATE_API_BASE_URL is not set.
const SERVER_API_BASE = `${env.PRIVATE_API_BASE_URL ?? PUBLIC_API_BASE_URL}/api/v1`;
export async function serverFetch<T>(
path: string,
cookies: Cookies,
@@ -17,6 +23,7 @@ export async function serverFetch<T>(
return apiFetch<T>(path, {
...init,
baseURL: SERVER_API_BASE,
headers: {
...headers,
...init?.headers

View File

@@ -16,12 +16,13 @@ export class ApiClientError extends Error {
export async function apiFetch<T>(
path: string,
init?: RequestInit & { fetch?: typeof globalThis.fetch }
init?: RequestInit & { fetch?: typeof globalThis.fetch; baseURL?: string }
): Promise<ApiResponse<T>> {
const fetchFn = init?.fetch ?? globalThis.fetch;
const { fetch: _, ...restInit } = init ?? {};
const { fetch: _, baseURL, ...restInit } = init ?? {};
const base = baseURL ?? API_BASE;
const res = await fetchFn(`${API_BASE}${path}`, {
const res = await fetchFn(`${base}${path}`, {
headers: {
'Content-Type': 'application/json',
...restInit.headers