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
|
||||
src/config.local.js
|
||||
|
||||
taxonomy-with-ids.de-DE*
|
||||
|
||||
# Local development notes
|
||||
dev-notes.md
|
||||
dev-notes.local.md
|
||||
@@ -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 = `<?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"
|
||||
alt="404 - Page Not Found"
|
||||
style={{
|
||||
width: '300px',
|
||||
height: '300px',
|
||||
maxWidth: '100%',
|
||||
height: 'auto',
|
||||
maxHeight: '300px',
|
||||
display: 'block',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Typography variant="body1" paragraph align="center">
|
||||
This page is no longer available.
|
||||
Diese Seite scheint es nicht mehr zu geben.
|
||||
</Typography>
|
||||
</>
|
||||
);
|
||||
|
||||
return <LegalPage title="Page Not Found" content={content} />;
|
||||
return <LegalPage content={content} />;
|
||||
};
|
||||
|
||||
export default NotFound404;
|
||||
@@ -309,6 +309,219 @@ export default {
|
||||
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;
|
||||
},
|
||||
hot: true,
|
||||
@@ -317,7 +530,18 @@ export default {
|
||||
historyApiFallback: {
|
||||
index: '/index.html',
|
||||
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: {
|
||||
logging: 'verbose',
|
||||
|
||||
Reference in New Issue
Block a user