Compare commits

...

2 Commits

5 changed files with 412 additions and 93 deletions

2
.gitignore vendored
View File

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

View File

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

92
src/PrerenderNotFound.js Normal file
View File

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

View File

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

View File

@@ -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',