mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
feat(landing-page): strengthen the vibe-design pillar + hub-and-spoke internal links (#5003)
* feat(landing-page): strengthen the vibe-design pillar + wire hub-and-spoke links The 4 vibe-design blog posts were splitting the bare "vibe design" query across each other (GSC: 3 pages clustered at pos 5-6 in the same locale), and the pillar (what-is-vibe-design) under-competed the top-ranking guides (Visily ~2,200 words + FAQ + decision matrix; Cerence ~2,100). Upgrade what-is-vibe-design into a proper pillar: - Add a "When vibe design is the right call — and when it isn't" decision section and a 7-question FAQ (PAA / featured-snippet surface), taking the body from ~1,150 to ~1,860 words. - Complete the hub-and-spoke: the pillar now links out to all three cluster posts (vibe-design-tools, vibe-design-vs-vibe-coding, vibe-design-with-stitch) in-context, consolidating topical authority on the pillar and clarifying to search engines which page owns the head term. - Sharpen the differentiator competitors miss — prompt to shipped code, open-source, agent-native — since Visily stops at a mockup and Cerence is an interview. Follow-up: re-translate the localized bodyHtml (keep-11 locales) to match. Locale pages keep their prior full translation until then (not broken, just one version behind). * fix(landing-page): keep vibe-design readingTime aligned with translated bodies readingTime is a shared field the blog template renders for every locale. Since this PR intentionally leaves the localized bodyHtml on the shorter prior copy until the translation follow-up, keep readingTime at 6 so the 10 localized pages stay accurate; it moves to 9 in the localization PR when every body grows to match the expanded English article. * feat(landing-page): per-locale blog readingTime override A shared readingTime can't be right for both the expanded English body and the localized bodyHtml still on the shorter prior translation (reviewer: 9 overstates the localized pages, 6 understates English). Add an optional per-locale readingTime to the blog i18n schema, render it in [slug].astro when present, set the English body to 9, and pin the ten rendered locales to 6 until the localization follow-up grows their bodies. Verified: EN shows 9 min, ja/de/es show 6. * fix(landing-page): use per-locale readingTime on the blog index too The detail page honored the per-locale readingTime override, but the blog index (and its /[locale]/ wrapper) still rendered the shared English value on both the featured and regular cards. Apply the same locale-aware lookup there so non-English index cards match their article. Verified: EN index card shows 9 min, JA shows 6. --------- Co-authored-by: Joey <276262049+xne998808-ai@users.noreply.github.com>
This commit is contained in:
@@ -140,6 +140,12 @@ const blog = defineCollection({
|
||||
category: z.string().optional(),
|
||||
body: z.string().optional(),
|
||||
bodyHtml: z.string().optional(),
|
||||
// Optional per-locale reading time. Set this when a localized
|
||||
// `bodyHtml` differs in length from the English Markdown (e.g. a
|
||||
// translation that hasn't caught up to an expanded English body)
|
||||
// so non-English readers see an accurate estimate instead of the
|
||||
// shared English `readingTime`.
|
||||
readingTime: z.number().int().positive().optional(),
|
||||
})
|
||||
.passthrough(),
|
||||
)
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -91,6 +91,13 @@ const localizedBodyHtml =
|
||||
) ??
|
||||
fallbackPost.bodyHtml;
|
||||
|
||||
// A localized bodyHtml can differ in length from the English Markdown; prefer a
|
||||
// per-locale readingTime when one is set so the estimate matches what the reader
|
||||
// actually sees, and fall back to the shared English value otherwise.
|
||||
const localizedReadingTime =
|
||||
(localizedPostI18n as { readingTime?: number } | undefined)?.readingTime ??
|
||||
post.data.readingTime;
|
||||
|
||||
// Build the table of contents. For pre-rendered bodyHtml (rich/localized
|
||||
// posts) inject heading ids and collect the TOC in one pass; for the Markdown
|
||||
// `<Content />` path use Astro's already-slugged `headings` array.
|
||||
@@ -260,7 +267,7 @@ const author = getBlogAuthor((post.data as { author?: string }).author);
|
||||
|
||||
<div class='post-meta'>
|
||||
<span>{fmtDate(post.data.date)}</span>
|
||||
<span>{post.data.readingTime} {ui.blog.minRead}</span>
|
||||
<span>{localizedReadingTime} {ui.blog.minRead}</span>
|
||||
<span>{localizedPostField('category')}</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -192,6 +192,15 @@ const postImages: Record<string, { src: string; alt: string }> = {
|
||||
},
|
||||
};
|
||||
const getPostImage = (id: string) => postImages[id];
|
||||
// Prefer a per-locale readingTime when a localized bodyHtml differs in length
|
||||
// from the English body (mirrors the detail page in `[slug].astro`), so cards
|
||||
// on non-English blog indexes show an accurate estimate.
|
||||
const localizedReadingTime = (post: (typeof posts)[number]) =>
|
||||
(
|
||||
post.data as typeof post.data & {
|
||||
i18n?: Record<string, { readingTime?: number }>;
|
||||
}
|
||||
).i18n?.[locale]?.readingTime ?? post.data.readingTime;
|
||||
const localizedPostField = (
|
||||
post: (typeof posts)[number],
|
||||
field: 'title' | 'summary' | 'category',
|
||||
@@ -283,7 +292,7 @@ const localizedPostField = (
|
||||
<span class='blog-feature-meta'>
|
||||
<span>{fmtDate(featuredPost.data.date)}</span>
|
||||
<span>·</span>
|
||||
<span>{featuredPost.data.readingTime} {ui.blog.minRead}</span>
|
||||
<span>{localizedReadingTime(featuredPost)} {ui.blog.minRead}</span>
|
||||
<span class='blog-feature-cta'>{ui.blog.readMore}</span>
|
||||
</span>
|
||||
</span>
|
||||
@@ -349,7 +358,7 @@ const localizedPostField = (
|
||||
<p class='blog-card-summary'>{localizedPostField(post, 'summary')}</p>
|
||||
<span class='blog-card-foot'>
|
||||
<span class='blog-card-date'>{fmtDate(post.data.date)}</span>
|
||||
<span class='blog-card-rt'>{post.data.readingTime} {ui.blog.minRead}</span>
|
||||
<span class='blog-card-rt'>{localizedReadingTime(post)} {ui.blog.minRead}</span>
|
||||
<span class='blog-card-cta'>{ui.blog.read}</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user