google search index
All checks were successful
CI — Website / Website — typecheck, build (pull_request) Successful in 57s
All checks were successful
CI — Website / Website — typecheck, build (pull_request) Successful in 57s
This commit is contained in:
69
packages/website/inject-seo.mjs
Normal file
69
packages/website/inject-seo.mjs
Normal file
@@ -0,0 +1,69 @@
|
||||
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const root = dirname(fileURLToPath(import.meta.url));
|
||||
const distHtml = join(root, 'dist', 'index.html');
|
||||
const { origin, routes } = JSON.parse(
|
||||
readFileSync(join(root, 'src', 'seo.routes.json'), 'utf8'),
|
||||
);
|
||||
|
||||
function apply(html, { title, description, url, robots }) {
|
||||
let out = html;
|
||||
out = out.replace(/<title>[\s\S]*?<\/title>/, `<title>${title}</title>`);
|
||||
out = out.replace(
|
||||
/(<meta\s+name="description"\s+content=")[^"]*(")/,
|
||||
`$1${description}$2`,
|
||||
);
|
||||
out = out.replace(/(<meta\s+name="robots"\s+content=")[^"]*(")/, `$1${robots}$2`);
|
||||
out = out.replace(/(<meta\s+property="og:title"\s+content=")[^"]*(")/, `$1${title}$2`);
|
||||
out = out.replace(
|
||||
/(<meta\s+property="og:description"\s+content=")[^"]*(")/,
|
||||
`$1${description}$2`,
|
||||
);
|
||||
out = out.replace(/(<meta\s+property="og:url"\s+content=")[^"]*(")/, `$1${url}$2`);
|
||||
out = out.replace(/(<meta\s+name="twitter:title"\s+content=")[^"]*(")/, `$1${title}$2`);
|
||||
out = out.replace(
|
||||
/(<meta\s+name="twitter:description"\s+content=")[^"]*(")/,
|
||||
`$1${description}$2`,
|
||||
);
|
||||
out = out.replace(/(<link\s+rel="canonical"\s+href=")[^"]*(")/, `$1${url}$2`);
|
||||
return out;
|
||||
}
|
||||
|
||||
const template = readFileSync(distHtml, 'utf8');
|
||||
if (!template.includes('rel="canonical"') || !template.includes('og:title')) {
|
||||
console.error('dist/index.html is missing SEO tags — check index.html');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const written = [];
|
||||
for (const route of routes) {
|
||||
const url = route.path === '/' ? `${origin}/` : `${origin}${route.path}`;
|
||||
const file = route.path === '/' ? 'index.html' : `${route.path.replace(/^\//, '')}/index.html`;
|
||||
const dest = join(root, 'dist', file);
|
||||
mkdirSync(dirname(dest), { recursive: true });
|
||||
writeFileSync(
|
||||
dest,
|
||||
apply(template, {
|
||||
title: route.title,
|
||||
description: route.description,
|
||||
url,
|
||||
robots: 'index, follow',
|
||||
}),
|
||||
);
|
||||
written.push(file);
|
||||
}
|
||||
|
||||
writeFileSync(
|
||||
join(root, 'dist', '404.html'),
|
||||
apply(template, {
|
||||
title: 'Page not found — LexAI',
|
||||
description: 'That address is not a LexAI page.',
|
||||
url: `${origin}/`,
|
||||
robots: 'noindex, follow',
|
||||
}),
|
||||
);
|
||||
written.push('404.html');
|
||||
|
||||
console.log('SEO HTML written for', written.join(', '));
|
||||
Reference in New Issue
Block a user