96 lines
3.3 KiB
TypeScript
96 lines
3.3 KiB
TypeScript
import fs from "fs";
|
|
import path from "path";
|
|
|
|
export interface SeoMeta {
|
|
title: string;
|
|
description: string;
|
|
canonicalUrl: string;
|
|
ogImage?: string;
|
|
jsonLd?: Record<string, unknown>;
|
|
}
|
|
|
|
export interface TemplateOptions {
|
|
meta: SeoMeta;
|
|
content: string; // HTML content to inject inside <div id="app">
|
|
assetTags: string; // <script>/<link> tags from built dist/index.html
|
|
}
|
|
|
|
/**
|
|
* Read built dist/index.html and extract <script> and <link rel="stylesheet"> tags.
|
|
* This ensures we always reference the correct hashed asset filenames.
|
|
*/
|
|
export function getAssetTags(): string {
|
|
const distPath = process.env.DIST_PATH || path.resolve(__dirname, "../dist");
|
|
const distIndex = path.join(distPath, "index.html");
|
|
const html = fs.readFileSync(distIndex, "utf-8");
|
|
const scriptMatch = html.match(/<script[^>]*src="[^"]*"[^>]*><\/script>/g);
|
|
const linkMatch = html.match(/<link[^>]*rel="stylesheet"[^>]*>/g);
|
|
return [...(linkMatch || []), ...(scriptMatch || [])].join("\n ");
|
|
}
|
|
|
|
export function renderTemplate(options: TemplateOptions): string {
|
|
const { meta, content, assetTags } = options;
|
|
const jsonLdScript = meta.jsonLd
|
|
? `\n <script type="application/ld+json">${JSON.stringify(
|
|
meta.jsonLd,
|
|
)}</script>`
|
|
: "";
|
|
|
|
return `<!DOCTYPE html>
|
|
<html lang="ru">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<meta name="yandex-verification" content="81ff150ccc5ab8c6" />
|
|
<title>${meta.title}</title>
|
|
<meta name="description" content="${meta.description}" />
|
|
<link rel="canonical" href="${meta.canonicalUrl}" />
|
|
<meta property="og:title" content="${meta.title}" />
|
|
<meta property="og:description" content="${meta.description}" />
|
|
<meta property="og:type" content="website" />
|
|
<meta property="og:url" content="${meta.canonicalUrl}" />
|
|
${
|
|
meta.ogImage
|
|
? `<meta property="og:image" content="${meta.ogImage}" />`
|
|
: ""
|
|
}
|
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet" />
|
|
${assetTags}
|
|
${jsonLdScript}
|
|
</head>
|
|
<body>
|
|
<!-- Yandex.Metrika counter -->
|
|
<script type="text/javascript">
|
|
(function (m, e, t, r, i, k, a) {
|
|
m[i] = m[i] || function () { (m[i].a = m[i].a || []).push(arguments); };
|
|
m[i].l = 1 * new Date();
|
|
for (var j = 0; j < document.scripts.length; j++) {
|
|
if (document.scripts[j].src === r) return;
|
|
}
|
|
(k = e.createElement(t)),
|
|
(a = e.getElementsByTagName(t)[0]),
|
|
(k.async = 1),
|
|
(k.src = r),
|
|
a.parentNode.insertBefore(k, a);
|
|
})(window, document, "script", "https://mc.yandex.ru/metrika/tag.js?id=112806558", "ym");
|
|
ym(112806558, "init", {
|
|
ssr: true,
|
|
webvisor: true,
|
|
clickmap: true,
|
|
ecommerce: "dataLayer",
|
|
referrer: document.referrer,
|
|
url: location.href,
|
|
accurateTrackBounce: true,
|
|
trackLinks: true,
|
|
});
|
|
</script>
|
|
<noscript><img src="https://mc.yandex.ru/watch/112806558" style="position:absolute;left:-9999px" alt="" /></noscript>
|
|
<!-- /Yandex.Metrika counter -->
|
|
<div id="app">
|
|
${content}
|
|
</div>
|
|
</body>
|
|
</html>`;
|
|
}
|