epicweb-dev / epicweb-dev/epic-stack

/img/* and /favicons/* 404 handler prevents static file serving when placed before dev/prod middleware

Open
#1,079 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
5.5k
Forks
461
Avg merge
2h 43m
Merged PRs (30d)
1

Description

Problem
The 404 handler for /img/* and /favicons/* routes intercepts requests before the static file serving middleware can handle them, causing images and favicons to return 404 errors.

```
app.get(['/img/*splat', '/favicons/*splat'], (_req, res) => {
// if we made it past the express.static for these, then we're missing something.
// So we'll just send a 404 and won't bother calling other middleware.
res.status(404).send('Not found')
})

```
Current Behavior
When this handler is placed before the dev/prod server setup static files at /img/* and /favicons/* paths return 404 errors, unless the full /public/img path is used.

Expected Behavior
The 404 handler should only trigger after Express has attempted to serve static files from the public directory.

Solution

The 404 handler needs to be moved after the static file middleware setup block , so that:

In development: Vite middleware attempts to serve the files first
In production: express.static attempts to serve the files first
Only if neither finds the file, the 404 handler responds

Example:
```
if (IS_DEV) {
console.log('Starting development server')
const viteDevServer = await import('vite').then((vite) =>
vite.createServer({
server: { middlewareMode: true },
// We tell Vite we are running a custom app instead of
// the SPA default so it doesn't run HTML middleware
appType: 'custom',
}),
)
app.use(viteDevServer.middlewares)
app.use(async (req, res, next) => {
try {
const source = await viteDevServer.ssrLoadModule('./server/app.ts')
return await source.app(req, res, next)
} catch (error) {
if (typeof error === 'object' && error instanceof Error) {
viteDevServer.ssrFixStacktrace(error)
}
next(error)
}
})
} else {
console.log('Starting production server')
// React Router fingerprints its assets so we can cache forever.
app.use(
'/assets',
express.static('build/client/assets', {
immutable: true,
maxAge: '1y',
fallthrough: false,
}),
)
// Everything else (like favicon.ico) is cached for an hour. You may want to be
// more aggressive with this caching.
app.use(express.static('build/client', { maxAge: '1h' }))
app.use(await import(BUILD_PATH).then((mod) => mod.app))
}

app.get(['/img/*splat', '/favicons/*splat'], (_req, res) => {
// if we made it past the express.static for these, then we're missing something.
// So we'll just send a 404 and won't bother calling other middleware.
res.status(404).send('Not found')
})
```

Contributor guide

Open the contributing guide

Research direction

Start with the server entry containing the IS_DEV development/production middleware block and review how it loads server/app.ts in development. Verify that Vite or express.static handles /img/* and /favicons/* before the fallback handler; done means existing static files are served and missing files still receive the 404 response.

Written by the indexing model from the issue text.

Assessment

Tech stack
express, typescript, vite
Domain
backend, web-dev
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.