diff --git a/.env.example b/.env.example index 50e7ab3..6e17de9 100644 --- a/.env.example +++ b/.env.example @@ -3,6 +3,9 @@ PORT=4000 PUBLIC_BASE_URL=http://localhost:4000 WEB_ORIGIN=http://localhost:3001 NEXT_PUBLIC_AUTH_MODE=demo +# Vite dev server Host allow-list (comma-separated, or * for any host). +# On Coolify set this to your public domain, e.g. altdock.app.amania.jp +VITE_ALLOWED_HOSTS=altdock.app.amania.jp # --- Storage (MinIO via docker compose) ------------------------------------- # Leave DATABASE_URL empty to run with an in-memory store instead of Postgres. diff --git a/docker-compose.yml b/docker-compose.yml index 57bc2fd..1931774 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -113,6 +113,8 @@ services: environment: NEXT_PUBLIC_API_BASE_URL: ${NEXT_PUBLIC_API_BASE_URL:-http://localhost:4000} NEXT_PUBLIC_AUTH_MODE: ${AUTH_MODE:-demo} + # Hosts allowed to reach the dev server (comma-separated, or * for any). + VITE_ALLOWED_HOSTS: ${VITE_ALLOWED_HOSTS:-altdock.app.amania.jp} ports: - "3001:3001" depends_on: diff --git a/vite.config.ts b/vite.config.ts index ae08f93..be0aaac 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -11,6 +11,19 @@ const { d1, r2 } = hostingConfig; // macOS Seatbelt blocks FSEvents, so Codex previews need polling for HMR. const isCodexSeatbeltSandbox = process.env.CODEX_SANDBOX === "seatbelt"; +// Vite 8 blocks unknown Host headers by default. In dev (incl. Coolify +// proxying a public domain to the dev server) the public hosts must be +// allow-listed. Accepts a comma-separated list, or "*"/"true" for any host. +function parseAllowedHosts(raw: string | undefined): boolean | string[] | undefined { + if (!raw) return undefined; + const value = raw.trim(); + if (!value || value === "*" || value === "true") return true; + return value + .split(",") + .map((host) => host.trim()) + .filter(Boolean); +} + const localBindingConfig = { main: "./worker/index.ts", compatibility_flags: ["nodejs_compat"], @@ -43,10 +56,18 @@ export default defineConfig(async () => { // Wrangler snapshots its log path while the Cloudflare plugin is imported. const { cloudflare } = await import("@cloudflare/vite-plugin"); + const allowedHosts = parseAllowedHosts(process.env.VITE_ALLOWED_HOSTS); + return { - server: isCodexSeatbeltSandbox - ? { watch: { useFsEvents: false, usePolling: true } } - : undefined, + server: + isCodexSeatbeltSandbox || allowedHosts + ? { + ...(isCodexSeatbeltSandbox + ? { watch: { useFsEvents: false, usePolling: true } } + : {}), + ...(allowedHosts ? { allowedHosts } : {}), + } + : undefined, plugins: [ vinext(), sites(),