feat: add OG images, favicon set, manifest and performance tuning
- Build-time Open Graph cards per post and a default card (Satori + Sharp) - Favicon/ICO/apple-touch/manifest icons generated from one SVG (npm run icons) - robots.txt and web manifest generated from site.json - Subset self-hosted fonts to Latin + Turkish (188 KB -> 66 KB) with preload - Inline the stylesheet to remove the render-blocking request - Post-build site audit (npm run check:site): h1, alt, SEO tags, links - Omit canonical on noindex pages Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
// Builds small self-hosted variable fonts (Latin + Turkish + symbols used in posts) into public/fonts/.
|
||||
// Sources: @fontsource-variable/{rubik,inter} (SIL OFL 1.1). Usage: npm run fonts
|
||||
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { createRequire } from 'node:module';
|
||||
import subsetFont from 'subset-font';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
const ranges = [
|
||||
[0x20, 0x7e], // Basic Latin
|
||||
[0xa0, 0xff], // Latin-1 (ç ö ü · ° × …)
|
||||
[0x11e, 0x11f], // Ğ ğ
|
||||
[0x130, 0x131], // İ ı
|
||||
[0x15e, 0x15f], // Ş ş
|
||||
[0x394, 0x394], // Δ
|
||||
[0x3a9, 0x3a9], // Ω
|
||||
[0x3bb, 0x3bc], // λ μ
|
||||
[0x2013, 0x2014], // – —
|
||||
[0x2018, 0x201e], // ‘ ’ ‚ “ ” „
|
||||
[0x2022, 0x2022], // •
|
||||
[0x2026, 0x2026], // …
|
||||
[0x2032, 0x2033], // ′ ″
|
||||
[0x20ba, 0x20ba], // ₺
|
||||
[0x2122, 0x2122], // ™
|
||||
[0x2190, 0x2193], // ← ↑ → ↓
|
||||
[0x2212, 0x2212], // −
|
||||
[0x2248, 0x2248], // ≈
|
||||
[0x2264, 0x2265], // ≤ ≥
|
||||
];
|
||||
const text = ranges
|
||||
.flatMap(([a, b]) => Array.from({ length: b - a + 1 }, (_, i) => String.fromCodePoint(a + i)))
|
||||
.join('');
|
||||
|
||||
// Fontsource ships each family split by unicode-range (latin / latin-ext). Each part is subset
|
||||
// on its own and keeps Fontsource's unicode-range, so browsers fetch only the parts a page needs.
|
||||
const families = [
|
||||
{ pkg: '@fontsource-variable/rubik', family: 'Rubik Variable', slug: 'rubik', wght: [400, 900] },
|
||||
{ pkg: '@fontsource-variable/inter', family: 'Inter Variable', slug: 'inter', wght: [400, 700] },
|
||||
];
|
||||
const parts = ['latin', 'latin-ext'];
|
||||
|
||||
/** unicode-range of one Fontsource part, read from the package's own CSS. */
|
||||
function unicodeRange(pkg, slug, part) {
|
||||
const css = readFileSync(require.resolve(`${pkg}/index.css`), 'utf8');
|
||||
const block = css
|
||||
.split('@font-face')
|
||||
.find((b) => b.includes(`${slug}-${part}-wght-normal.woff2`));
|
||||
const m = block?.match(/unicode-range:\s*([^;]+);/);
|
||||
if (!m) throw new Error(`unicode-range not found for ${slug} ${part}`);
|
||||
return m[1].trim();
|
||||
}
|
||||
|
||||
mkdirSync('public/fonts', { recursive: true });
|
||||
const faces = [];
|
||||
for (const f of families) {
|
||||
for (const part of parts) {
|
||||
const source = readFileSync(
|
||||
require.resolve(`${f.pkg}/files/${f.slug}-${part}-wght-normal.woff2`),
|
||||
);
|
||||
const out = await subsetFont(source, text, {
|
||||
targetFormat: 'woff2',
|
||||
variationAxes: { wght: { min: f.wght[0], max: f.wght[1] } },
|
||||
});
|
||||
const file = `${f.slug}-${part}.woff2`;
|
||||
writeFileSync(`public/fonts/${file}`, out);
|
||||
console.log(`${file}: ${source.length} → ${out.length} bytes`);
|
||||
faces.push(`@font-face {
|
||||
font-family: '${f.family}';
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: ${f.wght[0]} ${f.wght[1]};
|
||||
src: url('/fonts/${file}') format('woff2-variations');
|
||||
unicode-range: ${unicodeRange(f.pkg, f.slug, part)};
|
||||
}`);
|
||||
}
|
||||
}
|
||||
|
||||
writeFileSync(
|
||||
'src/styles/fonts.css',
|
||||
`/* Generated by scripts/subset-fonts.mjs — do not edit. Fonts: SIL OFL 1.1 (Rubik, Inter). */\n${faces.join('\n')}\n`,
|
||||
);
|
||||
console.log('Wrote src/styles/fonts.css');
|
||||
Reference in New Issue
Block a user