fix: Prevent /static and /uploads paths from falling through to React catch-all

This commit is contained in:
2026-03-09 19:10:48 -05:00
parent 1d4534374b
commit 25e4035436

View File

@@ -36,10 +36,15 @@ app.use(cors());
app.use(express.json()); app.use(express.json());
app.use(express.urlencoded({ extended: true })); app.use(express.urlencoded({ extended: true }));
// Static file serving // Static asset routes — registered BEFORE React catch-all so they are
// resolved directly and never fall through to index.html
app.use('/uploads', express.static(UPLOAD_PATH)); app.use('/uploads', express.static(UPLOAD_PATH));
app.use('/static', express.static(STATIC_PATH)); app.use('/static', express.static(STATIC_PATH));
// Explicit 404 for missing asset files so the catch-all never intercepts them
app.use('/uploads', (req, res) => res.status(404).json({ error: 'Upload not found' }));
app.use('/static', (req, res) => res.status(404).json({ error: 'Static asset not found' }));
// API Routes // API Routes
app.use('/api/dogs', require('./routes/dogs')); app.use('/api/dogs', require('./routes/dogs'));
app.use('/api/litters', require('./routes/litters')); app.use('/api/litters', require('./routes/litters'));
@@ -53,11 +58,15 @@ app.get('/api/health', (req, res) => {
}); });
// Serve React frontend in production // Serve React frontend in production
// The catch-all is intentionally placed AFTER all asset/API routes above.
// express.static(clientBuildPath) handles real build assets (JS/CSS chunks).
// The scoped '*' only fires for HTML5 client-side routes (e.g. /dogs, /litters).
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {
const clientBuildPath = path.join(__dirname, '../client/dist'); const clientBuildPath = path.join(__dirname, '../client/dist');
app.use(express.static(clientBuildPath)); app.use(express.static(clientBuildPath));
app.get('*', (req, res) => { // Only send index.html for non-asset, non-api paths
app.get(/^(?!\/(api|static|uploads)\/).*$/, (req, res) => {
res.sendFile(path.join(clientBuildPath, 'index.html')); res.sendFile(path.join(clientBuildPath, 'index.html'));
}); });
} }