- Changed the site URL in index.html, router, and server routes from 'https://ypchat.net' to 'https://www.ypchat.net' for consistency and improved SEO. - Updated related meta tags and constants to reflect the new URL structure. These changes ensure a unified domain reference throughout the application.
246 lines
9.0 KiB
JavaScript
246 lines
9.0 KiB
JavaScript
import { readFileSync, existsSync } from 'fs';
|
|
import { join, resolve } from 'path';
|
|
import { loadFeedback } from './feedback-store.js';
|
|
|
|
const SITE_URL = 'https://www.ypchat.net';
|
|
const DEFAULT_IMAGE = `${SITE_URL}/static/favicon.png`;
|
|
|
|
const seoData = {
|
|
'/': {
|
|
title: 'SingleChat - Chat, Single-Chat und Bildaustausch',
|
|
description: 'Willkommen auf SingleChat - deine erste Adresse für Chat, Single-Chat und Bildaustausch. Chatte mit Menschen aus aller Welt, finde neue Kontakte und teile Erinnerungen sicher und komfortabel.',
|
|
keywords: 'Chat, Single-Chat, Bildaustausch, Online-Chat, Singles, Kontakte, Community',
|
|
ogTitle: 'SingleChat - Chat, Single-Chat und Bildaustausch',
|
|
ogDescription: 'Willkommen auf SingleChat - deine erste Adresse für Chat, Single-Chat und Bildaustausch.',
|
|
ogType: 'website',
|
|
ogUrl: `${SITE_URL}/`,
|
|
ogImage: DEFAULT_IMAGE,
|
|
robots: 'index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1',
|
|
schema: {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'WebSite',
|
|
name: 'SingleChat',
|
|
url: `${SITE_URL}/`,
|
|
description: 'Willkommen auf SingleChat - deine erste Adresse für Chat, Single-Chat und Bildaustausch.',
|
|
inLanguage: 'de-DE'
|
|
}
|
|
},
|
|
'/partners': {
|
|
title: 'Partner - SingleChat',
|
|
description: 'Unsere Partner und befreundete Seiten. Entdecke weitere interessante Angebote und Communities.',
|
|
keywords: 'Partner, Links, befreundete Seiten, Community',
|
|
ogTitle: 'Partner - SingleChat',
|
|
ogDescription: 'Unsere Partner und befreundete Seiten.',
|
|
ogType: 'website',
|
|
ogUrl: `${SITE_URL}/partners`,
|
|
ogImage: DEFAULT_IMAGE,
|
|
robots: 'index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1',
|
|
schema: {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'CollectionPage',
|
|
name: 'Partner - SingleChat',
|
|
url: `${SITE_URL}/partners`,
|
|
description: 'Unsere Partner und befreundete Seiten. Entdecke weitere interessante Angebote und Communities.',
|
|
isPartOf: {
|
|
'@type': 'WebSite',
|
|
name: 'SingleChat',
|
|
url: `${SITE_URL}/`
|
|
},
|
|
inLanguage: 'de-DE'
|
|
}
|
|
},
|
|
'/feedback': {
|
|
title: 'Feedback - SingleChat',
|
|
description: 'Oeffentliche Rueckmeldungen, Meinungen und Verbesserungsvorschlaege zu SingleChat.',
|
|
keywords: 'SingleChat Feedback, Kommentare, Rueckmeldungen, Verbesserungsvorschlaege',
|
|
ogTitle: 'Feedback - SingleChat',
|
|
ogDescription: 'Oeffentliche Rueckmeldungen, Meinungen und Verbesserungsvorschlaege zu SingleChat.',
|
|
ogType: 'website',
|
|
ogUrl: `${SITE_URL}/feedback`,
|
|
ogImage: DEFAULT_IMAGE,
|
|
robots: 'index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1',
|
|
schema: {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'CollectionPage',
|
|
name: 'Feedback - SingleChat',
|
|
url: `${SITE_URL}/feedback`,
|
|
description: 'Oeffentliche Rueckmeldungen, Meinungen und Verbesserungsvorschlaege zu SingleChat.',
|
|
isPartOf: {
|
|
'@type': 'WebSite',
|
|
name: 'SingleChat',
|
|
url: `${SITE_URL}/`
|
|
},
|
|
inLanguage: 'de-DE'
|
|
}
|
|
}
|
|
};
|
|
|
|
function escapeHtml(value = '') {
|
|
return String(value)
|
|
.replace(/&/g, '&')
|
|
.replace(/"/g, '"')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>');
|
|
}
|
|
|
|
function upsertMetaTag(html, name, content, attribute = 'name') {
|
|
const escapedContent = escapeHtml(content);
|
|
const regex = new RegExp(`<meta\\s+${attribute}="${name}"[^>]*>`, 'g');
|
|
const tag = `<meta ${attribute}="${name}" content="${escapedContent}">`;
|
|
|
|
if (regex.test(html)) {
|
|
return html.replace(regex, tag);
|
|
}
|
|
|
|
return html.replace('</head>', ` ${tag}\n</head>`);
|
|
}
|
|
|
|
function upsertLinkTag(html, rel, href) {
|
|
const escapedHref = escapeHtml(href);
|
|
const regex = new RegExp(`<link\\s+rel="${rel}"[^>]*>`, 'g');
|
|
const tag = `<link rel="${rel}" href="${escapedHref}">`;
|
|
|
|
if (regex.test(html)) {
|
|
return html.replace(regex, tag);
|
|
}
|
|
|
|
return html.replace('</head>', ` ${tag}\n</head>`);
|
|
}
|
|
|
|
function upsertJsonLd(html, schema) {
|
|
const tag = schema
|
|
? `<script type="application/ld+json" id="seo-json-ld">${JSON.stringify(schema)}</script>`
|
|
: '<script type="application/ld+json" id="seo-json-ld"></script>';
|
|
|
|
if (html.includes('id="seo-json-ld"')) {
|
|
return html.replace(/<script type="application\/ld\+json" id="seo-json-ld">.*?<\/script>/s, tag);
|
|
}
|
|
|
|
return html.replace('</head>', ` ${tag}\n</head>`);
|
|
}
|
|
|
|
function generateHTML(route, meta, __dirname) {
|
|
const distIndexPath = join(__dirname, '../docroot/dist/index.html');
|
|
|
|
if (!existsSync(distIndexPath)) {
|
|
console.error('WARNUNG: Gebaute index.html nicht gefunden:', distIndexPath);
|
|
return null;
|
|
}
|
|
|
|
let html = readFileSync(distIndexPath, 'utf-8');
|
|
|
|
html = html.replace(/<title>.*?<\/title>/, `<title>${escapeHtml(meta.title)}</title>`);
|
|
html = upsertMetaTag(html, 'description', meta.description);
|
|
html = upsertMetaTag(html, 'keywords', meta.keywords);
|
|
html = upsertMetaTag(html, 'robots', meta.robots);
|
|
html = upsertMetaTag(html, 'theme-color', '#2f6f46');
|
|
|
|
html = upsertMetaTag(html, 'og:title', meta.ogTitle, 'property');
|
|
html = upsertMetaTag(html, 'og:description', meta.ogDescription, 'property');
|
|
html = upsertMetaTag(html, 'og:type', meta.ogType, 'property');
|
|
html = upsertMetaTag(html, 'og:url', meta.ogUrl, 'property');
|
|
html = upsertMetaTag(html, 'og:image', meta.ogImage, 'property');
|
|
html = upsertMetaTag(html, 'og:site_name', 'SingleChat', 'property');
|
|
html = upsertMetaTag(html, 'og:locale', 'de_DE', 'property');
|
|
|
|
html = upsertMetaTag(html, 'twitter:card', 'summary_large_image');
|
|
html = upsertMetaTag(html, 'twitter:title', meta.ogTitle);
|
|
html = upsertMetaTag(html, 'twitter:description', meta.ogDescription);
|
|
html = upsertMetaTag(html, 'twitter:image', meta.ogImage);
|
|
|
|
html = upsertLinkTag(html, 'canonical', meta.ogUrl);
|
|
html = upsertJsonLd(html, meta.schema);
|
|
|
|
if (route === '/feedback') {
|
|
const feedbackItems = loadFeedback(__dirname)
|
|
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
|
|
.slice(0, 20);
|
|
|
|
const feedbackMarkup = feedbackItems.length > 0
|
|
? feedbackItems.map((item) => {
|
|
const metaLine = [item.country, item.age, item.gender].filter(Boolean).join(' · ');
|
|
return `<article style="border:1px solid #d7dfd9;border-radius:12px;padding:14px 16px;margin-bottom:12px;background:#fff;">
|
|
<strong style="display:block;color:#18201b;">${escapeHtml(item.name || 'Anonym')}</strong>
|
|
${metaLine ? `<div style="font-size:12px;color:#637067;margin-top:4px;">${escapeHtml(metaLine)}</div>` : ''}
|
|
<div style="font-size:12px;color:#637067;margin-top:4px;">${escapeHtml(new Date(item.createdAt).toLocaleString('de-DE'))}</div>
|
|
<p style="margin-top:10px;color:#2c362f;white-space:pre-wrap;">${escapeHtml(item.comment)}</p>
|
|
</article>`;
|
|
}).join('\n')
|
|
: '<p>Noch kein Feedback vorhanden.</p>';
|
|
|
|
const preview = `<section style="max-width:960px;margin:24px auto;padding:0 16px;">
|
|
<h2 style="font:600 28px/1.15 sans-serif;color:#18201b;margin:0 0 10px;">Feedback zu SingleChat</h2>
|
|
<p style="font:400 15px/1.5 sans-serif;color:#4f5d54;margin:0 0 18px;">Oeffentliche Rueckmeldungen und Verbesserungsvorschlaege.</p>
|
|
${feedbackMarkup}
|
|
</section>`;
|
|
|
|
html = html.replace('<div id="app"></div>', `<div id="app">${preview}</div>`);
|
|
}
|
|
|
|
return html;
|
|
}
|
|
|
|
export function setupSEORoutes(app, __dirname) {
|
|
const IS_PRODUCTION = process.env.NODE_ENV === 'production';
|
|
|
|
if (IS_PRODUCTION) {
|
|
const distIndexPath = resolve(__dirname, '../docroot/dist/index.html');
|
|
|
|
Object.entries(seoData).forEach(([route, meta]) => {
|
|
app.get(route, (req, res) => {
|
|
const html = generateHTML(route, meta, __dirname);
|
|
if (html) {
|
|
res.send(html);
|
|
return;
|
|
}
|
|
|
|
if (existsSync(distIndexPath)) {
|
|
res.sendFile(distIndexPath);
|
|
return;
|
|
}
|
|
|
|
console.error('FEHLER: Gebaute index.html nicht gefunden:', distIndexPath);
|
|
res.status(500).send('Gebaute index.html nicht gefunden. Bitte führe "npm run build" aus.');
|
|
});
|
|
});
|
|
}
|
|
|
|
app.get('/robots.txt', (req, res) => {
|
|
const robotsTxt = `User-agent: *
|
|
Allow: /
|
|
Allow: /partners
|
|
Allow: /feedback
|
|
Disallow: /api/
|
|
Disallow: /static/logs/
|
|
Disallow: /mockup-redesign
|
|
|
|
Sitemap: ${SITE_URL}/sitemap.xml
|
|
`;
|
|
res.type('text/plain');
|
|
res.send(robotsTxt);
|
|
});
|
|
|
|
app.get('/sitemap.xml', (req, res) => {
|
|
const currentDate = new Date().toISOString().split('T')[0];
|
|
const urls = Object.entries(seoData)
|
|
.map(([route, meta]) => {
|
|
const priority = route === '/' ? '1.0' : '0.8';
|
|
const changefreq = route === '/' ? 'daily' : 'weekly';
|
|
return ` <url>
|
|
<loc>${meta.ogUrl}</loc>
|
|
<lastmod>${currentDate}</lastmod>
|
|
<changefreq>${changefreq}</changefreq>
|
|
<priority>${priority}</priority>
|
|
</url>`;
|
|
})
|
|
.join('\n');
|
|
|
|
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
${urls}
|
|
</urlset>`;
|
|
res.type('application/xml');
|
|
res.send(sitemap);
|
|
});
|
|
}
|