diff --git a/.gitignore b/.gitignore index 5bb675e..f69d527 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,8 @@ yarn-error.log* # Local configuration src/config.local.js +taxonomy-with-ids.de-DE* + # Local development notes dev-notes.md dev-notes.local.md \ No newline at end of file diff --git a/prerender/seo/feeds.cjs b/prerender/seo/feeds.cjs index e193cc1..b24a56b 100644 --- a/prerender/seo/feeds.cjs +++ b/prerender/seo/feeds.cjs @@ -48,131 +48,131 @@ const generateProductsXml = (allProductsData = [], baseUrl, config) => { const getGoogleProductCategory = (categoryId) => { const categoryMappings = { // Seeds & Plants - 689: "Home & Garden > Plants > Seeds", - 706: "Home & Garden > Plants", // Stecklinge (cuttings) - 376: "Home & Garden > Plants > Plant & Herb Growing Kits", // Grow-Sets + 689: "543561", // Seeds (Saatgut) + 706: "543561", // Stecklinge (cuttings) – ebenfalls Pflanzen/Saatgut + 376: "2802", // Grow-Sets – Pflanzen- & Kräuteranbausets // Headshop & Accessories - 709: "Arts & Entertainment > Hobbies & Creative Arts", // Headshop - 711: "Arts & Entertainment > Hobbies & Creative Arts", // Bongs - 714: "Arts & Entertainment > Hobbies & Creative Arts", // Zubehör - 748: "Arts & Entertainment > Hobbies & Creative Arts", // Köpfe - 749: "Arts & Entertainment > Hobbies & Creative Arts", // Chillums / Diffusoren / Kupplungen - 896: "Electronics > Electronics Accessories", // Vaporizer - 710: "Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils", // Grinder + 709: "4082", // Headshop – Rauchzubehör + 711: "4082", // Headshop > Bongs – Rauchzubehör + 714: "4082", // Headshop > Bongs > Zubehör – Rauchzubehör + 748: "4082", // Headshop > Bongs > Köpfe – Rauchzubehör + 749: "4082", // Headshop > Bongs > Chillums/Diffusoren/Kupplungen – Rauchzubehör + 896: "3151", // Headshop > Vaporizer – Vaporizer + 710: "5109", // Headshop > Grinder – Gewürzmühlen (Küchenhelfer) // Measuring & Packaging - 186: "Business & Industrial > Science & Laboratory", // Wiegen & Verpacken - 187: "Business & Industrial > Science & Laboratory > Lab Equipment", // Waagen - 346: "Home & Garden > Kitchen & Dining > Food Storage", // Vakuumbeutel - 355: "Home & Garden > Kitchen & Dining > Food Storage", // Boveda & Integra Boost - 407: "Home & Garden > Kitchen & Dining > Food Storage", // Grove Bags - 449: "Home & Garden > Kitchen & Dining > Food Storage", // Cliptütchen - 539: "Home & Garden > Kitchen & Dining > Food Storage", // Gläser & Dosen + 186: "5631", // Headshop > Wiegen & Verpacken – Aufbewahrung/Zubehör + 187: "4767", // Headshop > Waagen – Personenwaagen (Medizinisch) + 346: "7118", // Headshop > Vakuumbeutel – Vakuumierer-Beutel + 355: "606", // Headshop > Boveda & Integra Boost – Luftentfeuchter (nächstmögliche) + 407: "3561", // Headshop > Grove Bags – Aufbewahrungsbehälter + 449: "1496", // Headshop > Cliptütchen – Lebensmittelverpackungsmaterial + 539: "3110", // Headshop > Gläser & Dosen – Lebensmittelbehälter // Lighting & Equipment - 694: "Home & Garden > Lighting", // Lampen - 261: "Home & Garden > Lighting", // Lampenzubehör + 694: "3006", // Lampen – Lampen (Beleuchtung) + 261: "3006", // Zubehör > Lampenzubehör – Lampen // Plants & Growing - 691: "Home & Garden > Lawn & Garden > Fertilizers", // Dünger - 692: "Home & Garden > Lawn & Garden > Fertilizers", // Dünger - Zubehör - 693: "Sporting Goods > Outdoor Recreation > Camping & Hiking > Tents", // Zelte + 691: "500033", // Dünger – Dünger + 692: "5633", // Zubehör > Dünger-Zubehör – Zubehör für Gartenarbeit + 693: "5655", // Zelte – Zelte // Pots & Containers - 219: "Home & Garden > Decor > Planters & Pots", // Töpfe - 220: "Home & Garden > Decor > Planters & Pots", // Untersetzer - 301: "Home & Garden > Decor > Planters & Pots", // Stofftöpfe - 317: "Home & Garden > Decor > Planters & Pots", // Air-Pot - 364: "Home & Garden > Decor > Planters & Pots", // Kunststofftöpfe - 292: "Home & Garden > Decor > Planters & Pots", // Trays & Fluttische + 219: "113", // Töpfe – Blumentöpfe & Pflanzgefäße + 220: "3173", // Töpfe > Untersetzer – Gartentopfuntersetzer und Trays + 301: "113", // Töpfe > Stofftöpfe – (Blumentöpfe/Pflanzgefäße) + 317: "113", // Töpfe > Air-Pot – (Blumentöpfe/Pflanzgefäße) + 364: "113", // Töpfe > Kunststofftöpfe – (Blumentöpfe/Pflanzgefäße) + 292: "3568", // Bewässerung > Trays & Fluttische – Bewässerungssysteme // Ventilation & Climate - 703: "Home & Garden > Outdoor Power Tools", // Abluft-Sets - 247: "Home & Garden > Outdoor Power Tools", // Belüftung - 214: "Home & Garden > Outdoor Power Tools", // Umluft-Ventilatoren - 308: "Home & Garden > Outdoor Power Tools", // Ab- und Zuluft - 609: "Home & Garden > Outdoor Power Tools", // Schalldämpfer - 248: "Home & Garden > Pool & Spa > Pool & Spa Filters", // Aktivkohlefilter - 392: "Home & Garden > Pool & Spa > Pool & Spa Filters", // Zuluftfilter - 658: "Home & Garden > Climate Control > Dehumidifiers", // Luftbe- und entfeuchter - 310: "Home & Garden > Climate Control > Heating", // Heizmatten - 379: "Home & Garden > Household Supplies > Air Fresheners", // Geruchsneutralisation + 703: "2802", // Grow-Sets > Abluft-Sets – (verwendet Pflanzen-Kräuter-Anbausets) + 247: "1700", // Belüftung – Ventilatoren (Klimatisierung) + 214: "1700", // Belüftung > Umluft-Ventilatoren – Ventilatoren + 308: "1700", // Belüftung > Ab- und Zuluft – Ventilatoren + 609: "1700", // Belüftung > Ab- und Zuluft > Schalldämpfer – Ventilatoren + 248: "1700", // Belüftung > Aktivkohlefilter – Ventilatoren (nächstmögliche) + 392: "1700", // Belüftung > Ab- und Zuluft > Zuluftfilter – Ventilatoren + 658: "606", // Belüftung > Luftbe- und -entfeuchter – Luftentfeuchter + 310: "2802", // Anzucht > Heizmatten – Pflanzen- & Kräuteranbausets + 379: "5631", // Belüftung > Geruchsneutralisation – Haushaltsbedarf: Aufbewahrung // Irrigation & Watering - 221: "Home & Garden > Lawn & Garden > Watering Equipment", // Bewässerung - 250: "Home & Garden > Lawn & Garden > Watering Equipment", // Schläuche - 297: "Home & Garden > Lawn & Garden > Watering Equipment", // Pumpen - 354: "Home & Garden > Lawn & Garden > Watering Equipment", // Sprüher - 372: "Home & Garden > Lawn & Garden > Watering Equipment", // AutoPot - 389: "Home & Garden > Lawn & Garden > Watering Equipment", // Blumat - 405: "Home & Garden > Lawn & Garden > Watering Equipment", // Schläuche - 425: "Home & Garden > Lawn & Garden > Watering Equipment", // Wassertanks - 480: "Home & Garden > Lawn & Garden > Watering Equipment", // Tropfer - 519: "Home & Garden > Lawn & Garden > Watering Equipment", // Pumpsprüher + 221: "3568", // Bewässerung – Bewässerungssysteme (Gesamt) + 250: "6318", // Bewässerung > Schläuche – Gartenschläuche + 297: "500100", // Bewässerung > Pumpen – Bewässerung-/Sprinklerpumpen + 354: "3780", // Bewässerung > Sprüher – Sprinkler & Sprühköpfe + 372: "3568", // Bewässerung > AutoPot – Bewässerungssysteme + 389: "3568", // Bewässerung > Blumat – Bewässerungssysteme + 405: "6318", // Bewässerung > Schläuche – Gartenschläuche + 425: "3568", // Bewässerung > Wassertanks – Bewässerungssysteme + 480: "3568", // Bewässerung > Tropfer – Bewässerungssysteme + 519: "3568", // Bewässerung > Pumpsprüher – Bewässerungssysteme // Growing Media & Soils - 242: "Home & Garden > Lawn & Garden > Fertilizers", // Böden - 243: "Home & Garden > Lawn & Garden > Fertilizers", // Erde - 269: "Home & Garden > Lawn & Garden > Fertilizers", // Kokos - 580: "Home & Garden > Lawn & Garden > Fertilizers", // Perlite & Blähton + 242: "543677", // Böden – Gartenerde + 243: "543677", // Böden > Erde – Gartenerde + 269: "543677", // Böden > Kokos – Gartenerde + 580: "543677", // Böden > Perlite & Blähton – Gartenerde // Propagation & Starting - 286: "Home & Garden > Plants", // Anzucht - 298: "Home & Garden > Plants", // Steinwolltrays - 421: "Home & Garden > Plants", // Vermehrungszubehör - 489: "Home & Garden > Plants", // EazyPlug & Jiffy - 359: "Home & Garden > Outdoor Structures > Greenhouses", // Gewächshäuser + 286: "2802", // Anzucht – Pflanzen- & Kräuteranbausets + 298: "2802", // Anzucht > Steinwolltrays – Pflanzen- & Kräuteranbausets + 421: "2802", // Anzucht > Vermehrungszubehör – Pflanzen- & Kräuteranbausets + 489: "2802", // Anzucht > EazyPlug & Jiffy – Pflanzen- & Kräuteranbausets + 359: "3103", // Anzucht > Gewächshäuser – Gewächshäuser // Tools & Equipment - 373: "Home & Garden > Tools > Hand Tools", // GrowTool - 403: "Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils", // Messbecher & mehr - 259: "Home & Garden > Tools > Hand Tools", // Pressen - 280: "Home & Garden > Tools > Hand Tools", // Erntescheeren - 258: "Home & Garden > Tools", // Ernte & Verarbeitung - 278: "Home & Garden > Tools", // Extraktion - 302: "Home & Garden > Tools", // Erntemaschinen + 373: "3568", // Bewässerung > GrowTool – Bewässerungssysteme + 403: "3999", // Bewässerung > Messbecher & mehr – Messbecher & Dosierlöffel + 259: "756", // Zubehör > Ernte & Verarbeitung > Pressen – Nudelmaschinen + 280: "2948", // Zubehör > Ernte & Verarbeitung > Erntescheeren – Küchenmesser + 258: "684", // Zubehör > Ernte & Verarbeitung – Abfallzerkleinerer + 278: "5057", // Zubehör > Ernte & Verarbeitung > Extraktion – Slush-Eis-Maschinen + 302: "7332", // Zubehör > Ernte & Verarbeitung > Erntemaschinen – Gartenmaschinen // Hardware & Plumbing - 222: "Hardware > Plumbing Fixtures", // PE-Teile - 374: "Hardware > Plumbing Fixtures", // Verbindungsteile + 222: "3568", // Bewässerung > PE-Teile – Bewässerungssysteme + 374: "1700", // Belüftung > Ab- und Zuluft > Verbindungsteile – Ventilatoren // Electronics & Control - 314: "Electronics > Electronics Accessories", // Steuergeräte - 408: "Electronics > Electronics Accessories", // GrowControl - 344: "Business & Industrial > Science & Laboratory > Lab Equipment", // Messgeräte - 555: "Business & Industrial > Science & Laboratory > Lab Equipment", // Mikroskope + 314: "1700", // Belüftung > Steuergeräte – Ventilatoren + 408: "1700", // Belüftung > Steuergeräte > GrowControl – Ventilatoren + 344: "1207", // Zubehör > Messgeräte – Messwerkzeuge & Messwertgeber + 555: "4555", // Zubehör > Anbauzubehör > Mikroskope – Mikroskope // Camping & Outdoor - 226: "Sporting Goods > Outdoor Recreation > Camping & Hiking", // Zeltzubehör + 226: "5655", // Zubehör > Zeltzubehör – Zelte // Plant Care & Protection - 239: "Home & Garden > Lawn & Garden > Pest Control", // Pflanzenschutz - 240: "Home & Garden > Plants", // Anbauzubehör + 239: "4085", // Zubehör > Anbauzubehör > Pflanzenschutz – Herbizide + 240: "5633", // Zubehör > Anbauzubehör – Zubehör für Gartenarbeit // Office & Media - 424: "Business & Industrial > Office Supplies", // Etiketten & Schilder - 387: "Media > Books", // Literatur + 424: "4377", // Zubehör > Anbauzubehör > Etiketten & Schilder – Etiketten & Anhängerschilder + 387: "543541", // Zubehör > Anbauzubehör > Literatur – Bücher // General categories - 705: "Home & Garden", // Set-Konfigurator - 686: "Home & Garden", // Zubehör - 741: "Home & Garden", // Zubehör - 294: "Home & Garden", // Zubehör - 695: "Home & Garden", // Zubehör - 293: "Home & Garden", // Trockennetze - 4: "Home & Garden", // Sonstiges - 450: "Home & Garden", // Restposten + 705: "2802", // Grow-Sets > Set-Konfigurator – (ebenfalls Pflanzen-Anbausets) + 686: "1700", // Belüftung > Aktivkohlefilter > Zubehör – Ventilatoren + 741: "1700", // Belüftung > Ab- und Zuluft > Zubehör – Ventilatoren + 294: "3568", // Bewässerung > Zubehör – Bewässerungssysteme + 695: "5631", // Zubehör – Haushaltsbedarf: Aufbewahrung + 293: "5631", // Zubehör > Ernte & Verarbeitung > Trockennetze – Haushaltsbedarf: Aufbewahrung + 4: "5631", // Zubehör > Anbauzubehör > Sonstiges – Haushaltsbedarf: Aufbewahrung + 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 - if (!category || category.trim() === "") { - return "Home & Garden > Plants"; + // Validate that the category ID is not empty + if (!categoryId_str || categoryId_str.trim() === "") { + return "5631"; // Haushaltsbedarf: Aufbewahrung } - return category; + return categoryId_str; }; let productsXml = ` diff --git a/src/PrerenderNotFound.js b/src/PrerenderNotFound.js new file mode 100644 index 0000000..835be4f --- /dev/null +++ b/src/PrerenderNotFound.js @@ -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 }; \ No newline at end of file diff --git a/src/pages/NotFound404.js b/src/pages/NotFound404.js index b25e9f6..c4dcda2 100644 --- a/src/pages/NotFound404.js +++ b/src/pages/NotFound404.js @@ -16,19 +16,20 @@ const NotFound404 = () => { src="/assets/images/404.png" alt="404 - Page Not Found" style={{ + width: '300px', + height: '300px', maxWidth: '100%', - height: 'auto', - maxHeight: '300px', + display: 'block', }} /> - This page is no longer available. + Diese Seite scheint es nicht mehr zu geben. ); - return ; + return ; }; export default NotFound404; \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 453daec..831a1c0 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -312,27 +312,210 @@ export default { // Add middleware to handle /404 route BEFORE webpack-dev-server processing middlewares.unshift({ name: 'handle-404-route', - middleware: (req, res, next) => { + middleware: async (req, res, next) => { if (req.url === '/404') { - // Mark this request as a 404 and intercept the response - res.locals.is404 = true; + // Set up prerender environment + const { createRequire } = await import('module'); + const require = createRequire(import.meta.url); - // Override writeHead to force 404 status - const originalWriteHead = res.writeHead; - res.writeHead = function(statusCode, statusMessage, headers) { - // Force 404 status and no-cache headers - const newHeaders = { - ...headers, - 'Cache-Control': 'no-cache, no-store, must-revalidate', - 'Pragma': 'no-cache', - 'Expires': '0' + require('@babel/register')({ + presets: [ + ['@babel/preset-env', { targets: { node: 'current' } }], + '@babel/preset-react' + ], + extensions: ['.js', '.jsx'], + ignore: [/node_modules/] + }); + + // 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 - req.url = '/'; - next(); + if (!global.localStorage) { + global.localStorage = mockStorage; + } + 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 += ``; + }); + + // 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('
', `
${processedMarkup}
`) + .replace('', ``) + .replace('', ` + + ${scriptTags} + `); + + // 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 { next(); }