Skip to content

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

@leonardo-ornelas

Description

@leonardo-ornelas

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')
})

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions