Enhance 404 handling in webpack configuration with async middleware for prerendering. Updated NotFound404 page to improve user experience with localized messaging and responsive image styling. Added taxonomy ID mappings in feeds.cjs for better compliance and clarity in product categorization.

This commit is contained in:
seb
2025-07-07 02:12:19 +02:00
parent 8698816875
commit 9e14827c91
5 changed files with 386 additions and 108 deletions

2
.gitignore vendored
View File

@@ -56,6 +56,8 @@ yarn-error.log*
# Local configuration # Local configuration
src/config.local.js src/config.local.js
taxonomy-with-ids.de-DE*
# Local development notes # Local development notes
dev-notes.md dev-notes.md
dev-notes.local.md dev-notes.local.md

View File

@@ -48,131 +48,131 @@ const generateProductsXml = (allProductsData = [], baseUrl, config) => {
const getGoogleProductCategory = (categoryId) => { const getGoogleProductCategory = (categoryId) => {
const categoryMappings = { const categoryMappings = {
// Seeds & Plants // Seeds & Plants
689: "Home & Garden > Plants > Seeds", 689: "543561", // Seeds (Saatgut)
706: "Home & Garden > Plants", // Stecklinge (cuttings) 706: "543561", // Stecklinge (cuttings) – ebenfalls Pflanzen/Saatgut
376: "Home & Garden > Plants > Plant & Herb Growing Kits", // Grow-Sets 376: "2802", // Grow-Sets – Pflanzen- & Kräuteranbausets
// Headshop & Accessories // Headshop & Accessories
709: "Arts & Entertainment > Hobbies & Creative Arts", // Headshop 709: "4082", // Headshop – Rauchzubehör
711: "Arts & Entertainment > Hobbies & Creative Arts", // Bongs 711: "4082", // Headshop > Bongs – Rauchzubehör
714: "Arts & Entertainment > Hobbies & Creative Arts", // Zubehör 714: "4082", // Headshop > Bongs > Zubehör – Rauchzubehör
748: "Arts & Entertainment > Hobbies & Creative Arts", // Köpfe 748: "4082", // Headshop > Bongs > Köpfe – Rauchzubehör
749: "Arts & Entertainment > Hobbies & Creative Arts", // Chillums / Diffusoren / Kupplungen 749: "4082", // Headshop > Bongs > Chillums/Diffusoren/Kupplungen – Rauchzubehör
896: "Electronics > Electronics Accessories", // Vaporizer 896: "3151", // Headshop > Vaporizer – Vaporizer
710: "Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils", // Grinder 710: "5109", // Headshop > Grinder – Gewürzmühlen (Küchenhelfer)
// Measuring & Packaging // Measuring & Packaging
186: "Business & Industrial > Science & Laboratory", // Wiegen & Verpacken 186: "5631", // Headshop > Wiegen & Verpacken – Aufbewahrung/Zubehör
187: "Business & Industrial > Science & Laboratory > Lab Equipment", // Waagen 187: "4767", // Headshop > Waagen – Personenwaagen (Medizinisch)
346: "Home & Garden > Kitchen & Dining > Food Storage", // Vakuumbeutel 346: "7118", // Headshop > Vakuumbeutel – Vakuumierer-Beutel
355: "Home & Garden > Kitchen & Dining > Food Storage", // Boveda & Integra Boost 355: "606", // Headshop > Boveda & Integra Boost – Luftentfeuchter (nächstmögliche)
407: "Home & Garden > Kitchen & Dining > Food Storage", // Grove Bags 407: "3561", // Headshop > Grove Bags – Aufbewahrungsbehälter
449: "Home & Garden > Kitchen & Dining > Food Storage", // Cliptütchen 449: "1496", // Headshop > Cliptütchen – Lebensmittelverpackungsmaterial
539: "Home & Garden > Kitchen & Dining > Food Storage", // Gläser & Dosen 539: "3110", // Headshop > Gläser & Dosen – Lebensmittelbehälter
// Lighting & Equipment // Lighting & Equipment
694: "Home & Garden > Lighting", // Lampen 694: "3006", // Lampen – Lampen (Beleuchtung)
261: "Home & Garden > Lighting", // Lampenzubehör 261: "3006", // Zubehör > Lampenzubehör – Lampen
// Plants & Growing // Plants & Growing
691: "Home & Garden > Lawn & Garden > Fertilizers", // Dünger 691: "500033", // Dünger – Dünger
692: "Home & Garden > Lawn & Garden > Fertilizers", // Dünger - Zubehör 692: "5633", // Zubehör > Dünger-Zubehör – Zubehör für Gartenarbeit
693: "Sporting Goods > Outdoor Recreation > Camping & Hiking > Tents", // Zelte 693: "5655", // Zelte – Zelte
// Pots & Containers // Pots & Containers
219: "Home & Garden > Decor > Planters & Pots", // Töpfe 219: "113", // Töpfe – Blumentöpfe & Pflanzgefäße
220: "Home & Garden > Decor > Planters & Pots", // Untersetzer 220: "3173", // Töpfe > Untersetzer – Gartentopfuntersetzer und Trays
301: "Home & Garden > Decor > Planters & Pots", // Stofftöpfe 301: "113", // Töpfe > Stofftöpfe – (Blumentöpfe/Pflanzgefäße)
317: "Home & Garden > Decor > Planters & Pots", // Air-Pot 317: "113", // Töpfe > Air-Pot – (Blumentöpfe/Pflanzgefäße)
364: "Home & Garden > Decor > Planters & Pots", // Kunststofftöpfe 364: "113", // Töpfe > Kunststofftöpfe – (Blumentöpfe/Pflanzgefäße)
292: "Home & Garden > Decor > Planters & Pots", // Trays & Fluttische 292: "3568", // Bewässerung > Trays & Fluttische – Bewässerungssysteme
// Ventilation & Climate // Ventilation & Climate
703: "Home & Garden > Outdoor Power Tools", // Abluft-Sets 703: "2802", // Grow-Sets > Abluft-Sets – (verwendet Pflanzen-Kräuter-Anbausets)
247: "Home & Garden > Outdoor Power Tools", // Belüftung 247: "1700", // Belüftung – Ventilatoren (Klimatisierung)
214: "Home & Garden > Outdoor Power Tools", // Umluft-Ventilatoren 214: "1700", // Belüftung > Umluft-Ventilatoren – Ventilatoren
308: "Home & Garden > Outdoor Power Tools", // Ab- und Zuluft 308: "1700", // Belüftung > Ab- und Zuluft – Ventilatoren
609: "Home & Garden > Outdoor Power Tools", // Schalldämpfer 609: "1700", // Belüftung > Ab- und Zuluft > Schalldämpfer – Ventilatoren
248: "Home & Garden > Pool & Spa > Pool & Spa Filters", // Aktivkohlefilter 248: "1700", // Belüftung > Aktivkohlefilter – Ventilatoren (nächstmögliche)
392: "Home & Garden > Pool & Spa > Pool & Spa Filters", // Zuluftfilter 392: "1700", // Belüftung > Ab- und Zuluft > Zuluftfilter – Ventilatoren
658: "Home & Garden > Climate Control > Dehumidifiers", // Luftbe- und entfeuchter 658: "606", // Belüftung > Luftbe- und -entfeuchter – Luftentfeuchter
310: "Home & Garden > Climate Control > Heating", // Heizmatten 310: "2802", // Anzucht > Heizmatten – Pflanzen- & Kräuteranbausets
379: "Home & Garden > Household Supplies > Air Fresheners", // Geruchsneutralisation 379: "5631", // Belüftung > Geruchsneutralisation – Haushaltsbedarf: Aufbewahrung
// Irrigation & Watering // Irrigation & Watering
221: "Home & Garden > Lawn & Garden > Watering Equipment", // Bewässerung 221: "3568", // Bewässerung – Bewässerungssysteme (Gesamt)
250: "Home & Garden > Lawn & Garden > Watering Equipment", // Schläuche 250: "6318", // Bewässerung > Schläuche – Gartenschläuche
297: "Home & Garden > Lawn & Garden > Watering Equipment", // Pumpen 297: "500100", // Bewässerung > Pumpen – Bewässerung-/Sprinklerpumpen
354: "Home & Garden > Lawn & Garden > Watering Equipment", // Sprüher 354: "3780", // Bewässerung > Sprüher – Sprinkler & Sprühköpfe
372: "Home & Garden > Lawn & Garden > Watering Equipment", // AutoPot 372: "3568", // Bewässerung > AutoPot – Bewässerungssysteme
389: "Home & Garden > Lawn & Garden > Watering Equipment", // Blumat 389: "3568", // Bewässerung > Blumat – Bewässerungssysteme
405: "Home & Garden > Lawn & Garden > Watering Equipment", // Schläuche 405: "6318", // Bewässerung > Schläuche – Gartenschläuche
425: "Home & Garden > Lawn & Garden > Watering Equipment", // Wassertanks 425: "3568", // Bewässerung > Wassertanks – Bewässerungssysteme
480: "Home & Garden > Lawn & Garden > Watering Equipment", // Tropfer 480: "3568", // Bewässerung > Tropfer – Bewässerungssysteme
519: "Home & Garden > Lawn & Garden > Watering Equipment", // Pumpsprüher 519: "3568", // Bewässerung > Pumpsprüher – Bewässerungssysteme
// Growing Media & Soils // Growing Media & Soils
242: "Home & Garden > Lawn & Garden > Fertilizers", // Böden 242: "543677", // Böden – Gartenerde
243: "Home & Garden > Lawn & Garden > Fertilizers", // Erde 243: "543677", // Böden > Erde – Gartenerde
269: "Home & Garden > Lawn & Garden > Fertilizers", // Kokos 269: "543677", // Böden > Kokos – Gartenerde
580: "Home & Garden > Lawn & Garden > Fertilizers", // Perlite & Blähton 580: "543677", // Böden > Perlite & Blähton – Gartenerde
// Propagation & Starting // Propagation & Starting
286: "Home & Garden > Plants", // Anzucht 286: "2802", // Anzucht – Pflanzen- & Kräuteranbausets
298: "Home & Garden > Plants", // Steinwolltrays 298: "2802", // Anzucht > Steinwolltrays – Pflanzen- & Kräuteranbausets
421: "Home & Garden > Plants", // Vermehrungszubehör 421: "2802", // Anzucht > Vermehrungszubehör – Pflanzen- & Kräuteranbausets
489: "Home & Garden > Plants", // EazyPlug & Jiffy 489: "2802", // Anzucht > EazyPlug & Jiffy – Pflanzen- & Kräuteranbausets
359: "Home & Garden > Outdoor Structures > Greenhouses", // Gewächshäuser 359: "3103", // Anzucht > Gewächshäuser – Gewächshäuser
// Tools & Equipment // Tools & Equipment
373: "Home & Garden > Tools > Hand Tools", // GrowTool 373: "3568", // Bewässerung > GrowTool – Bewässerungssysteme
403: "Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils", // Messbecher & mehr 403: "3999", // Bewässerung > Messbecher & mehr – Messbecher & Dosierlöffel
259: "Home & Garden > Tools > Hand Tools", // Pressen 259: "756", // Zubehör > Ernte & Verarbeitung > Pressen – Nudelmaschinen
280: "Home & Garden > Tools > Hand Tools", // Erntescheeren 280: "2948", // Zubehör > Ernte & Verarbeitung > Erntescheeren – Küchenmesser
258: "Home & Garden > Tools", // Ernte & Verarbeitung 258: "684", // Zubehör > Ernte & Verarbeitung – Abfallzerkleinerer
278: "Home & Garden > Tools", // Extraktion 278: "5057", // Zubehör > Ernte & Verarbeitung > Extraktion – Slush-Eis-Maschinen
302: "Home & Garden > Tools", // Erntemaschinen 302: "7332", // Zubehör > Ernte & Verarbeitung > Erntemaschinen – Gartenmaschinen
// Hardware & Plumbing // Hardware & Plumbing
222: "Hardware > Plumbing Fixtures", // PE-Teile 222: "3568", // Bewässerung > PE-Teile – Bewässerungssysteme
374: "Hardware > Plumbing Fixtures", // Verbindungsteile 374: "1700", // Belüftung > Ab- und Zuluft > Verbindungsteile – Ventilatoren
// Electronics & Control // Electronics & Control
314: "Electronics > Electronics Accessories", // Steuergeräte 314: "1700", // Belüftung > Steuergeräte – Ventilatoren
408: "Electronics > Electronics Accessories", // GrowControl 408: "1700", // Belüftung > Steuergeräte > GrowControl – Ventilatoren
344: "Business & Industrial > Science & Laboratory > Lab Equipment", // Messgeräte 344: "1207", // Zubehör > Messgeräte – Messwerkzeuge & Messwertgeber
555: "Business & Industrial > Science & Laboratory > Lab Equipment", // Mikroskope 555: "4555", // Zubehör > Anbauzubehör > Mikroskope – Mikroskope
// Camping & Outdoor // Camping & Outdoor
226: "Sporting Goods > Outdoor Recreation > Camping & Hiking", // Zeltzubehör 226: "5655", // Zubehör > Zeltzubehör – Zelte
// Plant Care & Protection // Plant Care & Protection
239: "Home & Garden > Lawn & Garden > Pest Control", // Pflanzenschutz 239: "4085", // Zubehör > Anbauzubehör > Pflanzenschutz – Herbizide
240: "Home & Garden > Plants", // Anbauzubehör 240: "5633", // Zubehör > Anbauzubehör – Zubehör für Gartenarbeit
// Office & Media // Office & Media
424: "Business & Industrial > Office Supplies", // Etiketten & Schilder 424: "4377", // Zubehör > Anbauzubehör > Etiketten & Schilder – Etiketten & Anhängerschilder
387: "Media > Books", // Literatur 387: "543541", // Zubehör > Anbauzubehör > Literatur – Bücher
// General categories // General categories
705: "Home & Garden", // Set-Konfigurator 705: "2802", // Grow-Sets > Set-Konfigurator – (ebenfalls Pflanzen-Anbausets)
686: "Home & Garden", // Zubehör 686: "1700", // Belüftung > Aktivkohlefilter > Zubehör – Ventilatoren
741: "Home & Garden", // Zubehör 741: "1700", // Belüftung > Ab- und Zuluft > Zubehör – Ventilatoren
294: "Home & Garden", // Zubehör 294: "3568", // Bewässerung > Zubehör – Bewässerungssysteme
695: "Home & Garden", // Zubehör 695: "5631", // Zubehör – Haushaltsbedarf: Aufbewahrung
293: "Home & Garden", // Trockennetze 293: "5631", // Zubehör > Ernte & Verarbeitung > Trockennetze – Haushaltsbedarf: Aufbewahrung
4: "Home & Garden", // Sonstiges 4: "5631", // Zubehör > Anbauzubehör > Sonstiges – Haushaltsbedarf: Aufbewahrung
450: "Home & Garden", // Restposten 450: "5631", // Zubehör > Anbauzubehör > Restposten – Haushaltsbedarf: Aufbewahrung
}; };
const category = categoryMappings[categoryId] || "Home & Garden > Plants"; const categoryId_str = categoryMappings[categoryId] || "5631"; // Default to Haushaltsbedarf: Aufbewahrung
// Validate that the category is not empty or too generic // Validate that the category ID is not empty
if (!category || category.trim() === "") { if (!categoryId_str || categoryId_str.trim() === "") {
return "Home & Garden > Plants"; return "5631"; // Haushaltsbedarf: Aufbewahrung
} }
return category; return categoryId_str;
}; };
let productsXml = `<?xml version="1.0" encoding="UTF-8"?> let productsXml = `<?xml version="1.0" encoding="UTF-8"?>

92
src/PrerenderNotFound.js Normal file
View File

@@ -0,0 +1,92 @@
const React = require('react');
const {
Box,
AppBar,
Toolbar,
Container
} = require('@mui/material');
const Footer = require('./components/Footer.js').default;
const { Logo } = require('./components/header/index.js');
const NotFound404 = require('./pages/NotFound404.js').default;
class PrerenderNotFound extends React.Component {
render() {
return React.createElement(
Box,
{
sx: {
display: 'flex',
flexDirection: 'column',
minHeight: '100vh',
mb: 0,
pb: 0,
bgcolor: 'background.default'
}
},
React.createElement(
AppBar,
{ position: 'sticky', color: 'primary', elevation: 0, sx: { zIndex: 1100 } },
React.createElement(
Toolbar,
{ sx: { minHeight: 64, py: { xs: 0.5, sm: 0 } } },
React.createElement(
Container,
{
maxWidth: 'lg',
sx: {
display: 'flex',
alignItems: 'center',
px: { xs: 0, sm: 3 }
}
},
React.createElement(
Box,
{ sx: {
display: 'flex',
alignItems: 'center',
width: '100%',
flexDirection: { xs: 'column', sm: 'row' }
}
},
React.createElement(
Box,
{ sx: {
display: 'flex',
alignItems: 'center',
width: '100%',
justifyContent: { xs: 'space-between', sm: 'flex-start' },
minHeight: { xs: 52, sm: 'auto' },
px: { xs: 0, sm: 0 }
}
},
React.createElement(Logo)
),
// Reserve space for SearchBar on mobile (invisible placeholder)
React.createElement(
Box,
{ sx: {
display: { xs: 'block', sm: 'none' },
width: '100%',
mt: { xs: 1, sm: 0 },
mb: { xs: 0.5, sm: 0 },
px: { xs: 0, sm: 0 },
height: 40, // Reserve space for SearchBar
opacity: 0 // Invisible placeholder
}
}
)
)
)
)
),
React.createElement(
Box,
{ sx: { flexGrow: 1 } },
React.createElement(NotFound404)
),
React.createElement(Footer)
);
}
}
module.exports = { default: PrerenderNotFound };

View File

@@ -16,19 +16,20 @@ const NotFound404 = () => {
src="/assets/images/404.png" src="/assets/images/404.png"
alt="404 - Page Not Found" alt="404 - Page Not Found"
style={{ style={{
width: '300px',
height: '300px',
maxWidth: '100%', maxWidth: '100%',
height: 'auto', display: 'block',
maxHeight: '300px',
}} }}
/> />
</Box> </Box>
<Typography variant="body1" paragraph align="center"> <Typography variant="body1" paragraph align="center">
This page is no longer available. Diese Seite scheint es nicht mehr zu geben.
</Typography> </Typography>
</> </>
); );
return <LegalPage title="Page Not Found" content={content} />; return <LegalPage content={content} />;
}; };
export default NotFound404; export default NotFound404;

View File

@@ -312,27 +312,210 @@ export default {
// Add middleware to handle /404 route BEFORE webpack-dev-server processing // Add middleware to handle /404 route BEFORE webpack-dev-server processing
middlewares.unshift({ middlewares.unshift({
name: 'handle-404-route', name: 'handle-404-route',
middleware: (req, res, next) => { middleware: async (req, res, next) => {
if (req.url === '/404') { if (req.url === '/404') {
// Mark this request as a 404 and intercept the response // Set up prerender environment
res.locals.is404 = true; const { createRequire } = await import('module');
const require = createRequire(import.meta.url);
// Override writeHead to force 404 status require('@babel/register')({
const originalWriteHead = res.writeHead; presets: [
res.writeHead = function(statusCode, statusMessage, headers) { ['@babel/preset-env', { targets: { node: 'current' } }],
// Force 404 status and no-cache headers '@babel/preset-react'
const newHeaders = { ],
...headers, extensions: ['.js', '.jsx'],
'Cache-Control': 'no-cache, no-store, must-revalidate', ignore: [/node_modules/]
'Pragma': 'no-cache', });
'Expires': '0'
// Import React first and make it globally available
const React = require('react');
global.React = React; // Make React available globally for components that don't import it
// Set up minimal globals for prerender
if (!global.window) {
global.window = {};
}
if (!global.navigator) {
global.navigator = { userAgent: 'node.js' };
}
if (!global.URL) {
global.URL = require('url').URL;
}
if (!global.Blob) {
global.Blob = class MockBlob {
constructor(data, options) {
this.data = data;
this.type = options?.type || '';
}
}; };
return originalWriteHead.call(this, 404, statusMessage, newHeaders); }
// Mock browser storage APIs
const mockStorage = {
getItem: () => null,
setItem: () => {},
removeItem: () => {},
clear: () => {},
key: () => null,
length: 0
}; };
// Rewrite the URL to / so historyApiFallback can handle it if (!global.localStorage) {
req.url = '/'; global.localStorage = mockStorage;
next(); }
if (!global.sessionStorage) {
global.sessionStorage = mockStorage;
}
// Also add to window object for components that access it via window
global.window.localStorage = mockStorage;
global.window.sessionStorage = mockStorage;
// Import the dedicated prerender component
const PrerenderNotFound = require('./src/PrerenderNotFound.js').default;
// Create the prerender component
const component = React.createElement(PrerenderNotFound);
// Get only the essential bundles (not lazy-loaded chunks)
let jsBundles = [];
try {
const outputFileSystem = devServer.compiler.outputFileSystem;
const outputPath = devServer.compiler.outputPath;
const jsPath = path.join(outputPath, 'js');
if (outputFileSystem.existsSync && outputFileSystem.existsSync(jsPath)) {
const jsFiles = outputFileSystem.readdirSync(jsPath);
// Only include essential bundles in correct dependency order
const essentialBundles = [];
// 1. Runtime bundle (webpack runtime - must be first)
const runtimeFile = jsFiles.find(f => f.startsWith('runtime.') && f.endsWith('.bundle.js'));
if (runtimeFile) essentialBundles.push('/js/' + runtimeFile);
// 2. Vendor bundles (libraries that main depends on)
const reactFile = jsFiles.find(f => f.startsWith('react.') && f.endsWith('.bundle.js'));
if (reactFile) essentialBundles.push('/js/' + reactFile);
const emotionFile = jsFiles.find(f => f.startsWith('emotion.') && f.endsWith('.bundle.js'));
if (emotionFile) essentialBundles.push('/js/' + emotionFile);
const muiIconsCommonFile = jsFiles.find(f => f.startsWith('mui-icons-common.') && f.endsWith('.bundle.js'));
if (muiIconsCommonFile) essentialBundles.push('/js/' + muiIconsCommonFile);
const muiCoreFile = jsFiles.find(f => f.startsWith('mui-core.') && f.endsWith('.bundle.js'));
if (muiCoreFile) essentialBundles.push('/js/' + muiCoreFile);
const vendorFile = jsFiles.find(f => f.startsWith('vendor.') && f.endsWith('.bundle.js'));
if (vendorFile) essentialBundles.push('/js/' + vendorFile);
// 3. Common shared code
const commonFile = jsFiles.find(f => f.startsWith('common.') && f.endsWith('.chunk.js'));
if (commonFile) essentialBundles.push('/js/' + commonFile);
// 4. Main bundle (your app code - must be last)
const mainFile = jsFiles.find(f => f.startsWith('main.') && f.endsWith('.bundle.js'));
if (mainFile) essentialBundles.push('/js/' + mainFile);
jsBundles = essentialBundles;
}
} catch (error) {
console.warn('Could not read webpack output filesystem:', error.message);
}
// Fallback if we can't read the filesystem
if (jsBundles.length === 0) {
jsBundles = ['/js/runtime.bundle.js', '/js/main.bundle.js'];
}
// Render the page in memory only (no file writing in dev mode)
const ReactDOMServer = require('react-dom/server');
const { StaticRouter } = require('react-router');
const { CacheProvider } = require('@emotion/react');
const { ThemeProvider } = require('@mui/material/styles');
const createEmotionCache = require('./createEmotionCache.js').default;
const theme = require('./src/theme.js').default;
const createEmotionServer = require('@emotion/server/create-instance').default;
// Create fresh Emotion cache for this page
const cache = createEmotionCache();
const { extractCriticalToChunks } = createEmotionServer(cache);
// Wrap with StaticRouter to provide React Router context for Logo's Link components
const routedComponent = React.createElement(
StaticRouter,
{ location: '/404' },
component
);
const pageElement = React.createElement(
CacheProvider,
{ value: cache },
React.createElement(ThemeProvider, { theme: theme }, routedComponent)
);
// Render to string
const renderedMarkup = ReactDOMServer.renderToString(pageElement);
const emotionChunks = extractCriticalToChunks(renderedMarkup);
// Build the full HTML page
const templatePath = path.resolve(__dirname, 'public', 'index.html');
let template = fs.readFileSync(templatePath, 'utf8');
// Add JavaScript bundles
let scriptTags = '';
jsBundles.forEach(jsFile => {
scriptTags += `<script src="${jsFile}"></script>`;
});
// Add global CSS from src/index.css
let globalCss = '';
try {
globalCss = fs.readFileSync(path.resolve(__dirname, 'src', 'index.css'), 'utf8');
// Fix relative font paths for prerendered HTML (remove ../public to make them relative to public root)
globalCss = globalCss.replace(/url\('\.\.\/public/g, "url('");
} catch (error) {
console.warn('Could not read src/index.css:', error.message);
}
// Add inline CSS from emotion
let emotionCss = '';
if (emotionChunks.styles.length > 0) {
emotionChunks.styles.forEach(style => {
if (style.css) {
emotionCss += style.css;
}
});
}
// Combine all CSS
const inlineCss = globalCss + emotionCss;
// Use the rendered markup as-is (no regex replacements)
let processedMarkup = renderedMarkup;
// Replace placeholders in template
const finalHtml = template
.replace('<div id="root"></div>', `<div id="root">${processedMarkup}</div>`)
.replace('</head>', `<style>${inlineCss}</style></head>`)
.replace('</body>', `
<script>
window.__PRERENDER_FALLBACK__ = {path: '/404', content: ${JSON.stringify(processedMarkup)}, timestamp: ${Date.now()}};
</script>
${scriptTags}
</body>`);
// Serve the prerendered HTML with 404 status
res.status(404);
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
res.setHeader('Pragma', 'no-cache');
res.setHeader('Expires', '0');
return res.send(finalHtml);
// If we get here, prerender failed - let the error bubble up
throw new Error('404 prerender failed completely');
} else { } else {
next(); next();
} }