diff --git a/website/astro.config.mjs b/website/astro.config.mjs index 509198a7..ab6ff7d9 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -5,6 +5,7 @@ import sitemap from "@astrojs/sitemap"; import mdx from "@astrojs/mdx"; import react from "@astrojs/react"; import rehypeExternalLinks from "./src/utils/rehype-external-links.ts"; +import rehypeAdaptiveTables from "./src/utils/rehype-adaptive-tables.ts"; import playformCompress from "@playform/compress"; import markdownForAgents from "astro-markdown-for-agents"; import { markdownForAgentsOptions } from "./markdown-for-agents.config.mjs"; @@ -55,7 +56,7 @@ export default defineConfig({ }, site: SITE_BASE_URL, markdown: { - rehypePlugins: [rehypeExternalLinks], + rehypePlugins: [rehypeExternalLinks, rehypeAdaptiveTables], }, image: { domains: ["i.ytimg.com"], diff --git a/website/scripts/check-seo-meta.mjs b/website/scripts/check-seo-meta.mjs index 5180f6a6..b4ceba8a 100644 --- a/website/scripts/check-seo-meta.mjs +++ b/website/scripts/check-seo-meta.mjs @@ -1,6 +1,7 @@ #!/usr/bin/env node /** * Validates SEO title/description lengths for i18n page meta and blog frontmatter. + * Blog posts: use `metaTitle` when set, otherwise `title`. * * Latin locales: title 30–60, description 120–160 * CJK locales (ja-JP, ko-KR, zh-CN): title 20–60, description 70–160 @@ -82,6 +83,7 @@ function parseFrontmatter(raw) { if (end === -1) return null; const fm = raw.slice(3, end); const titleMatch = fm.match(/^title:\s*(.*)$/m); + const metaTitleMatch = fm.match(/^metaTitle:\s*(.*)$/m); const descMatch = fm.match(/^description:\s*(.*)$/m); if (!titleMatch || !descMatch) return null; @@ -98,6 +100,7 @@ function parseFrontmatter(raw) { return { title: unquote(titleMatch[1]), + metaTitle: metaTitleMatch ? unquote(metaTitleMatch[1]) : undefined, description: unquote(descMatch[1]), }; } @@ -122,7 +125,7 @@ function checkBlogs(errors) { continue; } const rel = path.relative(websiteRoot, file); - checkLength("title", locale, rel, meta.title, errors); + checkLength("title", locale, rel, meta.metaTitle ?? meta.title, errors); checkLength("description", locale, rel, meta.description, errors); } } diff --git a/website/src/components/features/blog/BlogPost.astro b/website/src/components/features/blog/BlogPost.astro index a92de4a1..cc32e89b 100644 --- a/website/src/components/features/blog/BlogPost.astro +++ b/website/src/components/features/blog/BlogPost.astro @@ -14,14 +14,17 @@ import { PEOPLE, isPersonId, personId } from "@/constants/people"; import { getPersonJsonLd } from "@/constants/person-jsonld"; import { organizationJsonLd } from "@/constants/default-jsonld"; import { resolveTwitterCreator } from "@/utils/resolve-blog-author"; +import { blogMetaTitle } from "@/utils/blog-titles"; import env from "@/config"; type Props = CollectionEntry<"blog">["data"] & { relatedPosts?: CollectionEntry<"blog">[]; + isUnpublishedPreview?: boolean; }; const { title, + metaTitle, description, pubDate, updatedDate, @@ -30,8 +33,11 @@ const { tags = [], author: authorId, relatedPosts = [], + isUnpublishedPreview = false, } = Astro.props; +const documentTitle = blogMetaTitle({ title, metaTitle }); + const currentLocale = getLocaleFromUrl(Astro.url.pathname); const t = await createTranslator(currentLocale); @@ -39,7 +45,7 @@ const blogIndexHref = getLocalizedPath(currentLocale, "/blog"); const author = authorId && isPersonId(authorId) ? PEOPLE[authorId] : undefined; const metadata = createMetadata({ - title, + title: documentTitle, description, pathname: Astro.url.pathname, imageUrl: heroImage, @@ -102,6 +108,14 @@ const jsonLd = JsonLdGraph({ ) } + { + isUnpublishedPreview && ( +
+ Unpublished +
+ ) + } +