Fix static serving — og.png was returning index.html
The server mounted static files at /assets only, so every top-level file fell through to the SPA catch-all. og.png, apple-touch-icon.png, icon-192.png and manifest.webmanifest each returned `200 text/html` containing the app shell. This is a nearly invisible failure. The app works. The tab shows an icon, because browsers cache the SVG. Nothing errors anywhere. But a link shared to iMessage, Slack or X fetches og.png, receives HTML, and renders a card with no image — which is the entire point of having made one. Now the whole dist directory is served, with /api excluded so routes are never answered from disk, and the SPA fallback still catching client-side routes that have no file behind them. Found by fetching the asset from an outside host instead of trusting that adding the file was enough. Verified: og.png is image/png at 53KB, the icons and manifest carry their real types, /margin and /capacity still receive the shell, and the API is unaffected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+25
-1
@@ -25,7 +25,31 @@ const app = createApp(config, db);
|
||||
// it on its own port with hot reload.
|
||||
const webDist = join(process.cwd(), 'apps/web/dist');
|
||||
if (existsSync(webDist)) {
|
||||
app.use('/assets/*', serveStatic({ root: './apps/web/dist' }));
|
||||
/*
|
||||
* Serve the ENTIRE dist directory, not just /assets.
|
||||
*
|
||||
* Serving only /assets and letting everything else fall through to the SPA
|
||||
* shell quietly breaks every top-level file: og.png, the icons, the manifest
|
||||
* and robots.txt each returned `200 text/html` containing index.html.
|
||||
*
|
||||
* That failure is close to invisible. The app works, the browser tab shows
|
||||
* an icon (from the SVG, which browsers cache aggressively), and nothing
|
||||
* errors — but a link shared to iMessage, Slack or X fetches og.png, gets
|
||||
* HTML, and renders a card with no image. Verified with a real request from
|
||||
* an outside host rather than assumed, because that is the only way this
|
||||
* class of bug shows up.
|
||||
*
|
||||
* serveStatic calls next() when there is no matching file, so unmatched
|
||||
* paths still reach the SPA fallback below.
|
||||
*/
|
||||
app.use('*', async (c, next) => {
|
||||
// API routes must never be answered from disk.
|
||||
if (new URL(c.req.url).pathname.startsWith('/api/')) return next();
|
||||
return serveStatic({ root: './apps/web/dist' })(c, next);
|
||||
});
|
||||
|
||||
// Client-side routes (/margin, /capacity, …) have no file on disk and must
|
||||
// receive the shell so the router can take over.
|
||||
app.get('*', serveStatic({ path: './apps/web/dist/index.html' }));
|
||||
console.log('[pig] serving front end from', webDist);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user