feat: add prerender-single-product script for single product rendering with i18n support
This commit is contained in:
202
prerender-single-product.cjs
Normal file
202
prerender-single-product.cjs
Normal file
@@ -0,0 +1,202 @@
|
||||
require("@babel/register")({
|
||||
presets: [
|
||||
["@babel/preset-env", { targets: { node: "current" } }],
|
||||
"@babel/preset-react",
|
||||
],
|
||||
extensions: [".js", ".jsx"],
|
||||
ignore: [/node_modules/],
|
||||
});
|
||||
|
||||
// Minimal globals for socket.io-client only - no JSDOM to avoid interference
|
||||
global.window = {}; // Minimal window object for productCache
|
||||
global.navigator = { userAgent: "node.js" };
|
||||
global.URL = require("url").URL;
|
||||
global.Blob = class MockBlob {
|
||||
constructor(data, options) {
|
||||
this.data = data;
|
||||
this.type = options?.type || "";
|
||||
}
|
||||
};
|
||||
|
||||
// Import modules
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const React = require("react");
|
||||
const io = require("socket.io-client");
|
||||
|
||||
// Initialize i18n for prerendering with German as default
|
||||
const i18n = require("i18next");
|
||||
const { initReactI18next } = require("react-i18next");
|
||||
|
||||
// Import translation (just German for testing)
|
||||
const translationDE = require("./src/i18n/locales/de/translation.js").default;
|
||||
|
||||
// Initialize i18n
|
||||
i18n
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
resources: {
|
||||
de: { translation: translationDE }
|
||||
},
|
||||
lng: 'de',
|
||||
fallbackLng: 'de',
|
||||
debug: false,
|
||||
interpolation: {
|
||||
escapeValue: false
|
||||
},
|
||||
react: {
|
||||
useSuspense: false
|
||||
}
|
||||
});
|
||||
|
||||
// Make i18n available globally
|
||||
global.i18n = i18n;
|
||||
|
||||
// Import prerender modules
|
||||
const config = require("./prerender/config.cjs");
|
||||
const shopConfig = require("./src/config.js").default;
|
||||
const { renderPage } = require("./prerender/renderer.cjs");
|
||||
const { generateProductMetaTags, generateProductJsonLd } = require("./prerender/seo.cjs");
|
||||
const { fetchProductDetails } = require("./prerender/data-fetching.cjs");
|
||||
|
||||
// Import product component
|
||||
const PrerenderProduct = require("./src/PrerenderProduct.js").default;
|
||||
|
||||
const renderSingleProduct = async (productSeoName) => {
|
||||
const socketUrl = "http://127.0.0.1:9303";
|
||||
console.log(`🔌 Connecting to socket at ${socketUrl}...`);
|
||||
|
||||
const socket = io(socketUrl, {
|
||||
path: "/socket.io/",
|
||||
transports: ["polling", "websocket"],
|
||||
reconnection: false,
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
console.error("❌ Timeout: Could not connect to backend after 15 seconds");
|
||||
socket.disconnect();
|
||||
reject(new Error("Connection timeout"));
|
||||
}, 15000);
|
||||
|
||||
socket.on("connect", async () => {
|
||||
console.log(`✅ Socket connected. Fetching product: ${productSeoName}`);
|
||||
|
||||
try {
|
||||
// Fetch product details
|
||||
const productDetails = await fetchProductDetails(socket, productSeoName);
|
||||
console.log(`📦 Product found: ${productDetails.product.name}`);
|
||||
|
||||
// Set up minimal global cache (empty for single product test)
|
||||
global.window.productCache = {};
|
||||
global.productCache = {};
|
||||
|
||||
// Create product component
|
||||
const productComponent = React.createElement(PrerenderProduct, {
|
||||
productData: productDetails,
|
||||
t: global.i18n.t.bind(global.i18n),
|
||||
});
|
||||
|
||||
// Generate metadata
|
||||
const actualSeoName = productDetails.product.seoName || productSeoName;
|
||||
const filename = `Artikel/${actualSeoName}`;
|
||||
const location = `/Artikel/${actualSeoName}`;
|
||||
const description = `Product "${productDetails.product.name}" (seoName: ${productSeoName})`;
|
||||
|
||||
const metaTags = generateProductMetaTags({
|
||||
...productDetails.product,
|
||||
seoName: actualSeoName,
|
||||
}, shopConfig.baseUrl, shopConfig);
|
||||
|
||||
const jsonLdScript = generateProductJsonLd({
|
||||
...productDetails.product,
|
||||
seoName: actualSeoName,
|
||||
}, shopConfig.baseUrl, shopConfig);
|
||||
|
||||
const combinedMetaTags = metaTags + "\n" + jsonLdScript;
|
||||
|
||||
// Render the page
|
||||
console.log(`🎨 Rendering product page...`);
|
||||
const success = renderPage(
|
||||
productComponent,
|
||||
location,
|
||||
filename,
|
||||
description,
|
||||
combinedMetaTags,
|
||||
true, // needsRouter
|
||||
config,
|
||||
false, // suppressLogs
|
||||
productDetails // productData for cache
|
||||
);
|
||||
|
||||
if (success) {
|
||||
const outputPath = path.resolve(__dirname, config.outputDir, `${filename}.html`);
|
||||
console.log(`✅ Product page rendered successfully!`);
|
||||
console.log(`📄 Output file: ${outputPath}`);
|
||||
console.log(`🌐 Test URL: http://localhost:3000/Artikel/${actualSeoName}`);
|
||||
|
||||
// Show file size
|
||||
if (fs.existsSync(outputPath)) {
|
||||
const stats = fs.statSync(outputPath);
|
||||
console.log(`📊 File size: ${Math.round(stats.size / 1024)}KB`);
|
||||
}
|
||||
} else {
|
||||
console.log(`❌ Failed to render product page`);
|
||||
}
|
||||
|
||||
clearTimeout(timeout);
|
||||
socket.disconnect();
|
||||
resolve(success);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Error fetching/rendering product: ${error.message}`);
|
||||
clearTimeout(timeout);
|
||||
socket.disconnect();
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("connect_error", (err) => {
|
||||
clearTimeout(timeout);
|
||||
console.error("❌ Socket connection error:", err);
|
||||
console.log("💡 Make sure the backend server is running on http://127.0.0.1:9303");
|
||||
reject(err);
|
||||
});
|
||||
|
||||
socket.on("error", (err) => {
|
||||
clearTimeout(timeout);
|
||||
console.error("❌ Socket error:", err);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Get product seoName from command line arguments
|
||||
const productSeoName = process.argv[2];
|
||||
|
||||
if (!productSeoName) {
|
||||
console.log("❌ Usage: node prerender-single-product.cjs <product-seo-name>");
|
||||
console.log("📝 Example: node prerender-single-product.cjs led-grow-light-600w");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`🚀 Starting single product prerender test...`);
|
||||
console.log(`🎯 Product SEO name: ${productSeoName}`);
|
||||
console.log(`🔧 Mode: ${config.isProduction ? 'PRODUCTION' : 'DEVELOPMENT'}`);
|
||||
console.log(`📁 Output directory: ${config.outputDir}`);
|
||||
|
||||
renderSingleProduct(productSeoName)
|
||||
.then((success) => {
|
||||
if (success) {
|
||||
console.log(`\n🎉 Single product prerender completed successfully!`);
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.log(`\n💥 Single product prerender failed!`);
|
||||
process.exit(1);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(`\n💥 Single product prerender failed:`, error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user