Complete SEO Guide for Next.js 15: Meta Tags, Structured Data, and Core Web Vitals
SEO for an app usually means three things at once: tell search engines what each URL is, give social apps a decent preview when someone shares a link, and make pages fast enough that Google’s quality signals do not work against you. Next.js gives you hooks for all of that; the hard part is keeping one consistent setup as the codebase changes.
This is how I structure it in a real project—not every possible tactic, but the pieces that tend to matter and stay correct over time.
Terms worth aligning on#
- Meta tags — Data in the HTML
<head>(title, description, Open Graph tags for Facebook/LinkedIn-style previews, Twitter card fields, etc.). They do not “trick” Google by themselves; they describe the page honestly so crawlers and share dialogs know what to show. - Canonical URL — The URL you consider the “real” one when duplicates exist (e.g. with or without trailing slash). You set it so search engines consolidate signals to one address.
- Structured data (JSON-LD) — A small JSON blob in the page that uses schema.org vocabulary. Search engines use it to understand type of content (article, product, breadcrumbs). It should match what a human sees on the page; empty marketing schema does not help.
- Sitemap — An XML list of URLs you care about for discovery. Robots.txt tells crawlers what they are allowed to fetch; it is not the same as a sitemap, but both should agree with what you actually publish.
- Core Web Vitals — Google’s user-facing metrics (roughly: loading, interactivity, layout stability). Slow or janky pages can rank worse even if titles and descriptions are perfect.
If you already know these, skim; if not, the rest will make more sense with that map in mind.
The failure mode I watch for#
The usual break is not “we forgot one meta tag.” It is metadata defined in more than one place—a bit in a layout, a bit in a helper nobody owns, and a line in the README that went stale. Refactors then change one path and not the others, and you only notice when previews or Search Console look wrong.
So I aim for one typed description of “what this page should say to the world” and a single place that turns that into Next.js Metadata (and JSON-LD where needed).
A layout that stayed maintainable#
Concretely:
lib/seo.ts— Types and builders (title, description, canonical, OG image URL, article timestamps, etc.).components/seo/SEOHead.tsx(or similar) — Maps that object into theMetadatashape the App Router expects.- Route-level
generateMetadata— Only fills in what is unique to that route (e.g. this post’s title andlastmod).
Pages stay about content; SEO shape stays in one pipeline.
What I customize per blog post#
For posts I typically set:
- Title and meta description (what you want in results and shares).
- Canonical URL for that post.
- Social preview image and, when relevant, publish/update times and section/tags for Open Graph context.
Shared defaults (site name, base Twitter handle, fallback OG image) live in one config—not copy-pasted per file.
Structured data: accurate beats impressive#
I ship JSON-LD that matches the site I actually have:
Organization+WebSiteonce in the root layout (who owns the site, main URL).BlogPostingon article pages with dates and headline aligned to the visible article.BreadcrumbListonly if the breadcrumbs on screen match.ItemListon listing pages when it reflects a real list of links.
I skip types that imply features we do not have (fake products, reviews, etc.). Validators exist for a reason; misleading schema can be ignored or penalized.
Sitemap and robots from the same list as the UI#
The same source that lists published posts (or pages) should drive:
- The blog index and tag pages.
- Sitemap entries and
lastModifieddates. - Excluding drafts or unlisted content.
If the sitemap is hand-edited while the app pulls from a database or files, they will drift. One generator from one list fixes that class of bug.
Core Web Vitals in practice#
You do not need to chase every millisecond on day one. In App Router projects, the wins that usually move the needle:
- Stable layout (reserve space for images/fonts so content does not jump—CLS).
- Reasonable image formats and sizes (LCP is often image-heavy).
- Avoid shipping a huge client bundle on the first load of a content page if it can be mostly server-rendered.
Performance and SEO overlap here: slow pages get fewer engaged users, and search systems notice that.
Before I merge or publish#
Quick pass:
- Canonical and OG URL match the real page.
- Preview image and description look right in a sharing debugger (Twitter/Facebook have tools; Slack often follows OG).
noindexonly where intentional.- JSON-LD validates and matches the visible headline and dates.
- Sitemap includes new URLs when they should be discoverable.
Takeaway#
You do not need a perfect “SEO architecture.” You need one pipeline, honest metadata, structured data that matches the UI, and performance good enough not to undermine the rest. That is the core of making Next.js 15 play nicely with meta tags, structured data, and Core Web Vitals without turning SEO into a side project of its own.