From ae01d421919667d64799647894e5487812ab1b4d Mon Sep 17 00:00:00 2001 From: APAW Agent Sync Date: Fri, 15 May 2026 21:19:59 +0100 Subject: [PATCH] fix(content-length): remove Bun.file, use c.html(plain text) for dynamic routes Bun.file() returns Response without Content-Length, causing 12s Nginx wait and console warnings. serveStatic({ root }) is correct for /admin/*.html. c.html(await Bun.file(...).text()) is correct for SPA fallback routes where path != filename. Refs: production server --- src/server/index.ts | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/server/index.ts b/src/server/index.ts index 24d380f..c55839c 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -1794,29 +1794,27 @@ const adminHtmlAuthDisabled = async (c: any, next: any) => { } // Serve static files and SPA routes (clean URLs without .html) -// Admin component files - using Bun.file() directly to avoid serveStatic content-length bug -app.get('/admin/sidebar.html', async (c) => new Response(Bun.file('./public/admin/sidebar.html'))) -app.get('/admin/topbar.html', async (c) => new Response(Bun.file('./public/admin/topbar.html'))) -app.get('/admin/dashboard.html', async (c) => new Response(Bun.file('./public/admin/dashboard.html'))) -app.get('/admin/properties.html', async (c) => new Response(Bun.file('./public/admin/properties.html'))) -app.get('/admin/leads.html', async (c) => new Response(Bun.file('./public/admin/leads.html'))) -app.get('/admin/testimonials.html', async (c) => new Response(Bun.file('./public/admin/testimonials.html'))) -app.get('/admin/faq.html', async (c) => new Response(Bun.file('./public/admin/faq.html'))) -app.get('/admin/services.html', async (c) => new Response(Bun.file('./public/admin/services.html'))) -app.get('/admin/settings.html', async (c) => new Response(Bun.file('./public/admin/settings.html'))) -app.get('/admin/users.html', async (c) => new Response(Bun.file('./public/admin/users.html'))) -app.get('/admin/analytics.html', async (c) => new Response(Bun.file('./public/admin/analytics.html'))) -app.get('/admin/traffic.html', async (c) => new Response(Bun.file('./public/admin/traffic.html'))) +// Static assets (URL path matches file path under ./public) +app.get('/css/*', serveStatic({ root: './public' })) +app.get('/js/*', serveStatic({ root: './public' })) +app.get('/images/*', serveStatic({ root: './public' })) +app.get('/uploads/*', serveStatic({ root: './public' })) +app.get('/src/i18n/*', serveStatic({ root: '.' })) -// SPA routes - using Bun.file() for static files to avoid content-length bugs -app.get('/property/*', async (c) => new Response(Bun.file('./public/property.html'))) -app.get('/catalog', async (c) => new Response(Bun.file('./public/catalog.html'))) -app.get('/catalog.html', async (c) => new Response(Bun.file('./public/catalog.html'))) -app.get('/admin', async (c) => new Response(Bun.file('./public/admin.html'))) -app.get('/login', async (c) => new Response(Bun.file('./public/login.html'))) +// Admin HTML components - all served under /admin/* from ./public/admin/ +app.get('/admin/*', serveStatic({ root: './public' })) + +// Static HTML pages where URL path directly matches filename +app.get('/catalog.html', serveStatic({ root: './public' })) + +// SPA fallback routes (dynamic paths) - use c.html to ensure correct Content-Length +app.get('/property/*', async (c) => c.html(await Bun.file('./public/property.html').text())) +app.get('/catalog', async (c) => c.html(await Bun.file('./public/catalog.html').text())) +app.get('/admin', async (c) => c.html(await Bun.file('./public/admin.html').text())) +app.get('/login', async (c) => c.html(await Bun.file('./public/login.html').text())) // Fallback to index.html for all other routes -app.get('*', async (c) => new Response(Bun.file('./public/index.html'))) +app.get('*', async (c) => c.html(await Bun.file('./public/index.html').text())) // Start server const port = parseInt(process.env.PORT || '8080')