Skip to content

feat: add a public blog entry and SEO-ready content foundation #189

Description

@Che-Zhu

Problem

The landing-page header currently presents Blog as a disabled placeholder rather than a usable navigation destination. Fulling also has no /blog route, article content pipeline, or article-level SEO metadata, so the project has no first-party surface for explaining the product, publishing technical material, or building organic search visibility.

Current code context:

  • app/(landing)/_components/landing-client.tsx renders a disabled Blog, coming soon button.
  • There is no /blog or /blog/[slug] route.
  • The repository has no MDX/content loader or external CMS integration.
  • app/layout.tsx only defines global title and description metadata.
  • There is currently no blog-aware sitemap, canonical URL, Open Graph article metadata, BlogPosting structured data, or RSS feed.

Proposed first iteration

Use repository-owned MDX content and statically rendered Next.js App Router pages. This keeps the public blog independent of authentication, databases, and new hosted services while the project is still maintained primarily through Git.

Suggested shape:

content/blog/*.mdx
app/(marketing)/blog/page.tsx
app/(marketing)/blog/[slug]/page.tsx
lib/blog/*

Each published post should expose validated metadata such as title, description, slug, publication/update dates, author, tags, cover image, draft state, and optional canonical URL.

A structured CMS such as Sanity can be evaluated later if Fulling needs non-developer editing, scheduled publishing, draft previews, or collaborative editorial workflows. The public URLs and content model should make that migration possible without changing published slugs.

Scope

  • Replace the disabled landing-header Blog control with an accessible link to /blog.
  • Add a public blog index and individual post routes on the primary Fulling domain.
  • Add a repository-owned MDX content loader with metadata validation and deterministic post ordering.
  • Exclude drafts and future-dated posts from production output.
  • Add an initial real article so the entry does not lead to an empty placeholder.
  • Match the active design system in docs/design.md and provide responsive reading layouts.
  • Generate per-post title, description, canonical, Open Graph, and Twitter metadata.
  • Emit BlogPosting JSON-LD with author, dates, image, and canonical URL.
  • Include published posts in the production sitemap with accurate lastModified values.
  • Provide an RSS or Atom feed.
  • Ensure Vercel Preview deployments are not indexable while the production domain remains indexable.

Out of scope

  • External CMS adoption or a migration UI.
  • Google Ads, GA4, Google Tag Manager, or conversion tracking.
  • Comments, memberships, newsletter delivery, or a site-wide search service.
  • Bulk AI-generated SEO content.

Acceptance criteria

  • Selecting Blog from the landing-page header navigates to a functional /blog page.
  • /blog lists only published posts in reverse chronological order and links to stable article URLs.
  • A production article renders correctly without client-side data fetching, authentication, database access, or new required environment variables.
  • Every article has unique metadata, a canonical URL, social-preview metadata, and valid BlogPosting JSON-LD.
  • Published posts appear in the sitemap and feed; drafts and future posts do not.
  • Preview deployment URLs are marked noindex and do not become canonical URLs.
  • The blog index and article layout work at supported desktop, tablet, and mobile widths and meet basic keyboard and semantic-heading accessibility expectations.
  • Lint, tests, and the production build pass.

Follow-up direction

After the first content cluster is published, add Search Console verification and define a real product conversion before introducing paid acquisition or analytics tags. CMS adoption should be decided from editorial workflow needs rather than made a prerequisite for launching the blog.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions