Compare commits
2 Commits
987de641e4
...
9e14827c91
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e14827c91 | ||
|
|
8698816875 |
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;
|
||||||
@@ -309,6 +309,219 @@ export default {
|
|||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add middleware to handle /404 route BEFORE webpack-dev-server processing
|
||||||
|
middlewares.unshift({
|
||||||
|
name: 'handle-404-route',
|
||||||
|
middleware: async (req, res, next) => {
|
||||||
|
if (req.url === '/404') {
|
||||||
|
// Set up prerender environment
|
||||||
|
const { createRequire } = await import('module');
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
|
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 || '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock browser storage APIs
|
||||||
|
const mockStorage = {
|
||||||
|
getItem: () => null,
|
||||||
|
setItem: () => {},
|
||||||
|
removeItem: () => {},
|
||||||
|
clear: () => {},
|
||||||
|
key: () => null,
|
||||||
|
length: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
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 += `<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 {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return middlewares;
|
return middlewares;
|
||||||
},
|
},
|
||||||
hot: true,
|
hot: true,
|
||||||
@@ -317,7 +530,18 @@ export default {
|
|||||||
historyApiFallback: {
|
historyApiFallback: {
|
||||||
index: '/index.html',
|
index: '/index.html',
|
||||||
disableDotRule: true,
|
disableDotRule: true,
|
||||||
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml']
|
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
|
||||||
|
rewrites: [
|
||||||
|
// Exclude prerendered routes from SPA fallback
|
||||||
|
{ from: /^\/Kategorie\//, to: function(context) {
|
||||||
|
return context.parsedUrl.pathname;
|
||||||
|
}},
|
||||||
|
{ from: /^\/Artikel\//, to: function(context) {
|
||||||
|
return context.parsedUrl.pathname;
|
||||||
|
}},
|
||||||
|
// All other routes should fallback to React SPA
|
||||||
|
{ from: /^\/(?!api|socket\.io|assets|js|css|favicon\.ico).*$/, to: '/index.html' }
|
||||||
|
]
|
||||||
},
|
},
|
||||||
client: {
|
client: {
|
||||||
logging: 'verbose',
|
logging: 'verbose',
|
||||||
|
|||||||
Reference in New Issue
Block a user