generateMetadata on each blog and project route

brenthaskins.com uses generateMetadata in app/blog/[slug]/page.tsx and project pages so each URL exports its own title, description, canonical link, and Open Graph article times from frontmatter—not the root layout defaults alone.

Root layout metadata is the fallback. Every public URL should define its own.

Blog posts

app/blog/[slug]/page.tsx:

  • generateStaticParams from all slugs in content/blog
  • generateMetadata({ params }) reads frontmatter
  • Canonical: https://brenthaskins.com/blog/{slug} unless canonical override in frontmatter

Two fields serve two jobs:

FieldConsumer
descriptionMeta / OG / Twitter snippet length
summaryVisible paragraph on page (see summary post)

Projects

Case studies pull from content/projects.ts—unique title and description per slug for /projects/rally, /projects/formably, etc.

Build-time implication

Posts are static at build. New markdown requires deploy to update metadata—expected for this portfolio.

Not a ranking guarantee

Unique metadata prevents duplicate-title confusion in Search Console. It does not by itself produce rankings.

Brent Haskins — verify with curl -sI or view-source on production after adding a post.

Questions people ask about this topic.

How is metadata set per blog post on brenthaskins.com?

generateMetadata loads the post by slug via getBlogPost, then returns title from post.title, description from post.description, alternates.canonical as SITE_URL plus /blog/slug, and openGraph with type article, publishedTime from post.date, and authors Brent Haskins. Twitter card uses summary_large_image with the same description.

Does each project case study have unique metadata?

Project routes under app/projects/[slug]/page.tsx generate metadata from content/projects.ts fields—title, summary, and slug—so case studies do not share the homepage title. Sitemap lists those URLs separately from blog posts.

Referenced sources