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:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -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
|
||||||
@@ -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
92
src/PrerenderNotFound.js
Normal 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 };
|
||||||
@@ -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;
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user