Implement dynamic meta tag generation for horoscope pages, including daily, weekly, and monthly predictions, and optimize the OG image for improved social sharing previews. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 23852c00-4779-460a-9e0c-d09fee4b6c92 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: dea1fe98-0dfb-4e47-9b94-3c50decbba8a Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/f209e72a-0939-48fa-84fc-57854de71967/23852c00-4779-460a-9e0c-d09fee4b6c92/OPD8Ro3 Replit-Helium-Checkpoint-Created: true
164 lines
7.3 KiB
TypeScript
164 lines
7.3 KiB
TypeScript
import { type Express } from "express";
|
||
import { createServer as createViteServer, createLogger } from "vite";
|
||
import { type Server } from "http";
|
||
import viteConfig from "../vite.config";
|
||
import fs from "fs";
|
||
import path from "path";
|
||
import { nanoid } from "nanoid";
|
||
import { storage } from "./storage";
|
||
|
||
const viteLogger = createLogger();
|
||
|
||
function escapeHtml(str: string): string {
|
||
return str.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
||
}
|
||
|
||
function ogImageUrl(coverImage: string, baseUrl: string): string {
|
||
if (!coverImage) return "";
|
||
let imgPath = coverImage;
|
||
if (imgPath.endsWith(".webp")) {
|
||
imgPath = imgPath.replace(/\.webp$/, ".jpg");
|
||
}
|
||
return imgPath.startsWith("http") ? imgPath : `${baseUrl}${imgPath}`;
|
||
}
|
||
|
||
function stripExistingMeta(html: string): string {
|
||
html = html.replace(/<meta\s+property="og:[^"]*"[^>]*>\s*/gi, "");
|
||
html = html.replace(/<meta\s+name="twitter:[^"]*"[^>]*>\s*/gi, "");
|
||
html = html.replace(/<meta\s+name="description"[^>]*>\s*/gi, "");
|
||
html = html.replace(/<meta\s+name="keywords"[^>]*>\s*/gi, "");
|
||
html = html.replace(/<link\s+rel="canonical"[^>]*>\s*/gi, "");
|
||
return html;
|
||
}
|
||
|
||
export async function setupVite(server: Server, app: Express) {
|
||
const serverOptions = {
|
||
middlewareMode: true,
|
||
hmr: { server, path: "/vite-hmr" },
|
||
allowedHosts: true as const,
|
||
};
|
||
|
||
const vite = await createViteServer({
|
||
...viteConfig,
|
||
configFile: false,
|
||
customLogger: {
|
||
...viteLogger,
|
||
error: (msg, options) => {
|
||
viteLogger.error(msg, options);
|
||
process.exit(1);
|
||
},
|
||
},
|
||
server: serverOptions,
|
||
appType: "custom",
|
||
});
|
||
|
||
app.use(vite.middlewares);
|
||
|
||
app.use("/{*path}", async (req, res, next) => {
|
||
const url = req.originalUrl;
|
||
const canonicalBase = "https://folx.tv";
|
||
|
||
try {
|
||
const clientTemplate = path.resolve(
|
||
import.meta.dirname,
|
||
"..",
|
||
"client",
|
||
"index.html",
|
||
);
|
||
|
||
let template = await fs.promises.readFile(clientTemplate, "utf-8");
|
||
template = template.replace(
|
||
`src="/src/main.tsx"`,
|
||
`src="/src/main.tsx?v=${nanoid()}"`,
|
||
);
|
||
|
||
const articleMatch = url.match(/^\/article\/([^?#]+)/);
|
||
if (articleMatch) {
|
||
try {
|
||
const slug = decodeURIComponent(articleMatch[1]);
|
||
const article = await storage.getArticleBySlug(slug);
|
||
if (article) {
|
||
const articleUrl = `${canonicalBase}/article/${article.slug}`;
|
||
const imageUrl = ogImageUrl(article.coverImage || "", canonicalBase);
|
||
const finalImage = imageUrl || `${canonicalBase}/og-image.jpg`;
|
||
|
||
template = stripExistingMeta(template);
|
||
|
||
const ogTags = [
|
||
`<meta property="og:title" content="${escapeHtml(article.title)}" />`,
|
||
`<meta property="og:description" content="${escapeHtml(article.excerpt)}" />`,
|
||
`<meta property="og:type" content="article" />`,
|
||
`<meta property="og:url" content="${escapeHtml(articleUrl)}" />`,
|
||
`<meta property="og:image" content="${escapeHtml(finalImage)}" />`,
|
||
`<meta property="og:image:secure_url" content="${escapeHtml(finalImage)}" />`,
|
||
`<meta property="og:image:width" content="800" />`,
|
||
`<meta property="og:image:height" content="450" />`,
|
||
`<meta property="og:image:type" content="image/jpeg" />`,
|
||
`<meta property="og:site_name" content="Folx Music Television" />`,
|
||
`<meta property="og:locale" content="de_DE" />`,
|
||
`<meta property="fb:pages" content="1428520781492675" />`,
|
||
`<meta name="twitter:card" content="summary_large_image" />`,
|
||
`<meta name="twitter:title" content="${escapeHtml(article.title)}" />`,
|
||
`<meta name="twitter:description" content="${escapeHtml(article.excerpt)}" />`,
|
||
`<meta name="twitter:image" content="${escapeHtml(finalImage)}" />`,
|
||
`<meta name="description" content="${escapeHtml(article.excerpt)}" />`,
|
||
`<meta name="keywords" content="Volksmusik, Schlager, ${escapeHtml(article.title)}, ${escapeHtml(article.category || 'News')}, FOLX TV" />`,
|
||
`<link rel="canonical" href="${escapeHtml(articleUrl)}" />`,
|
||
`<title>${escapeHtml(article.title)} - Volksmusik & Schlager | Folx Music Television</title>`,
|
||
].join("\n ");
|
||
|
||
template = template.replace(/<title>[^<]*<\/title>/, ogTags);
|
||
}
|
||
} catch (e) {
|
||
}
|
||
}
|
||
|
||
if (url.match(/^\/horoskop(\/|$|\?)/)) {
|
||
template = stripExistingMeta(template);
|
||
const signMatch = url.match(/^\/horoskop\/([^?#]+)/);
|
||
const signName = signMatch ? decodeURIComponent(signMatch[1]) : null;
|
||
const capitalSign = signName ? signName.charAt(0).toUpperCase() + signName.slice(1) : null;
|
||
|
||
const title = capitalSign
|
||
? `${capitalSign} Horoskop – Tages-, Wochen- & Monatshoroskop | Folx Music Television`
|
||
: "Horoskop – Tages-, Wochen- & Monatshoroskop für alle Sternzeichen | Folx Music Television";
|
||
const desc = capitalSign
|
||
? `${capitalSign} Horoskop: Tägliches, wöchentliches und monatliches Horoskop. Liebe, Beruf & Gesundheit bei FOLX TV.`
|
||
: "Kostenloses Tageshoroskop, Wochenhoroskop & Monatshoroskop für alle 12 Sternzeichen. Liebe, Beruf, Gesundheit bei FOLX TV.";
|
||
const horoUrl = `${canonicalBase}${signName ? `/horoskop/${signName}` : "/horoskop"}`;
|
||
const horoImage = `${canonicalBase}/images/horoskop-og.jpg`;
|
||
|
||
const horoTags = [
|
||
`<meta property="og:title" content="${escapeHtml(title)}" />`,
|
||
`<meta property="og:description" content="${escapeHtml(desc)}" />`,
|
||
`<meta property="og:type" content="website" />`,
|
||
`<meta property="og:url" content="${escapeHtml(horoUrl)}" />`,
|
||
`<meta property="og:image" content="${escapeHtml(horoImage)}" />`,
|
||
`<meta property="og:image:secure_url" content="${escapeHtml(horoImage)}" />`,
|
||
`<meta property="og:image:width" content="1200" />`,
|
||
`<meta property="og:image:height" content="675" />`,
|
||
`<meta property="og:image:type" content="image/jpeg" />`,
|
||
`<meta property="og:site_name" content="Folx Music Television" />`,
|
||
`<meta property="og:locale" content="de_DE" />`,
|
||
`<meta name="twitter:card" content="summary_large_image" />`,
|
||
`<meta name="twitter:title" content="${escapeHtml(title)}" />`,
|
||
`<meta name="twitter:description" content="${escapeHtml(desc)}" />`,
|
||
`<meta name="twitter:image" content="${escapeHtml(horoImage)}" />`,
|
||
`<meta name="description" content="${escapeHtml(desc)}" />`,
|
||
`<meta name="keywords" content="Horoskop, Tageshoroskop, Wochenhoroskop, Monatshoroskop, Sternzeichen, Volksmusik, FOLX TV${capitalSign ? `, ${capitalSign}` : ""}" />`,
|
||
`<link rel="canonical" href="${escapeHtml(horoUrl)}" />`,
|
||
`<title>${escapeHtml(title)}</title>`,
|
||
].join("\n ");
|
||
|
||
template = template.replace(/<title>[^<]*<\/title>/, horoTags);
|
||
}
|
||
|
||
const page = await vite.transformIndexHtml(url, template);
|
||
res.status(200).set({ "Content-Type": "text/html" }).end(page);
|
||
} catch (e) {
|
||
vite.ssrFixStacktrace(e as Error);
|
||
next(e);
|
||
}
|
||
});
|
||
}
|