Suivant.js SEO

How to faire a Suivant.js site crawlable, indexable, and rankable — the two routers, rendering modes (SSG/SSR/ISR/Server Components), the App Router Metadata API, sitemap.ts, robots.ts, suivant/image, suivant/lien, and the mistakes que quietly break it.

Première publication : 26 juin 2026 · Dernière mise à jour : 3 août 2026 · Advanced
Langues

Suivant.js solves the hardest JavaScript SEO problems by par défaut — si vous utiliser it correct. The App Router's Server Components and SSG/ISR put content in the HTML so there's aucun render-queue delay; the native Metadata API resolves titles, canonicals, and Ouvrir Graph on le serveur; sitemap.ts and robots.ts are fichier conventions. The framework donne vous the infrastructure but writes none of votre tags pour vous. The échecs are predictable: manquant metadataBase, aucun priority on the LCP image, metadata exported from a Client Component (silently ne fait pashing), and 404 views returning 200.

TL;DR — Suivant.js solves the hardest JavaScript SEO problems by par défaut quand vous utiliser it correct. The App Router defaults to Server Components and supports SSG/SSR/ISR — tout of qui ship rendered HTML, so there’s aucun render-queue delay. Définir metadata with the native Metadata API (metadata / generateMetadata), and remember metadataBase or votre canonicals and OG images go relative. Utiliser app/sitemap.ts and app/robots.ts, pre-build dynamic routes with generateStaticParams, définir priority on the LCP image, and garder liens as réel next/link anchors. Lune pages Router is aussi SEO-capable via next/head. The échecs are predictable — and la plupart of les aren’t Suivant.js’s fault, they’re yours.

Où Suivant.js fits

Suivant.js is a React framework, so everything in JavaScript SEO s’applique. Ce que rend it worth its propre guide is que Suivant.js ships first-class réponses to la plupart JS-SEO problems: server rendering, static generation, a metadata system, and sitemap/robots conventions. The hard partie isn’t si Google peut lire it — Google has rendered JavaScript pour années — it’s choosing the correct rendering mode and pas leaving the SEO basics unwired. Ce is a specialized cas of CMS headless SEO: the CMS barely matters, the frontend’s rendering decisions decide everything.

Un framing to garder straight: “this is a Next.js site” doesn’t tell vous how quelconque unique URL is delivered. Rendering mode, mise en cache, and Server/Client Component boundaries are définir per route (parfois per segment) — a project peut mix a static marketing page, an SSR product page, and a Client Component dashboard. Don’t extrapolate un route’s behavior to “the whole app”; tester the spécifique URL.

Two routers, two sets of mechanics

Suivant.js has two routers, and ils handle SEO differently:

  • Pages Router (the older model) — données fetching via getStaticProps / getServerSideProps; metadata via <Head> from next/head (or the next-seo package); aucun Server Components.
  • App Router (v13+, the current and recommended approach) — React Server Components by par défaut; the native Metadata API (metadata export / generateMetadata); fichier conventions pour app/sitemap.ts and app/robots.ts; generateStaticParams pour dynamic routes. Evidence for this claim The App Router uses Server Components and supports generateStaticParams plus metadata file conventions. Scope: Current Next.js App Router behavior; route rendering can become dynamic based on APIs used. Confidence: high · Verified: Next.js: Server and Client Components Next.js: generateStaticParams

Les deux peut rank bien. The App Router donne vous a cleaner, integrated metadata system (aucun next/head juggling) and Server Components out of the box, qui is pourquoi I’d reach pour it on a nouveau construire. But “App Router or you can’t do SEO” is a myth — plenty of Pages Router sites rank fine.

Rendering modes and ce que chaque signifie pour le SEO

How Google handles JavaScript is a three-phase pipeline — explorer, alors a deferred render wave, alors index. “Tout pages with a 200 Code d’état HTTP are sent to the rendering queue.” The whole game in Suivant.js is choosing a mode que puts votre content in the HTML avant que render wave, so there’s nothing to wait pour. Evidence for this claim Google crawls, renders, and indexes JavaScript pages, and successful pages can enter the rendering queue. Scope: Google Search processing, not a promise that a URL will be indexed. Confidence: high · Verified: Google: JavaScript SEO basics

  • Static Site Generation (SSG) — pages pre-rendered at construire temps. HTML is immédiatement disponible with aucun render-queue risk. Meilleur pour content que doesn’t modifier every minute. generateStaticParams() (App Router) / getStaticPaths() (Pages Router) decides qui dynamic routes obtenir pre-built.
  • Incremental Static Regeneration (ISR) — static pages que revalidate après a définir interval (export const revalidate = 3600). Robots d’exploration obtenir static HTML with low TTFB and le contenu stays fresh. A strong par défaut — with un trap: après the window expires the suivant requête (possibly Googlebot) encore obtient the stale page; the fresh version sert on la requête après que. Pour genuinely volatile données (prices, stock), SSR is safer.
  • Rendu côté serveur (SSR) — HTML rendered per requête. Robots d’exploration obtenir entièrement rendered HTML immédiatement; the tradeoff is server latency, so watch TTFB and LCP. export const dynamic = 'force-dynamic' or en utilisant request-time APIs (cookies, headers) opts a route into SSR.
  • React Server Components (App Router par défaut) — render on le serveur and send HTML; aucun JavaScript ships pour the component itself. Content is in the initial réponse with aucun hydration gap. Ce is the meilleur par défaut pour le SEO. Interactivity lives in Client Components marked 'use client'.
  • Rendu côté client (CSR) — rendered entirely in le navigateur. Googlebot peut index it après the render wave (median ~10 seconds, but the 90th percentile stretches to hours), and autre robots d’exploration — Bingbot, AI bots, social preview bots — may obtenir an vide page. In the App Router, CSR is opt-in ('use client'); in lune pages Router, éviter fetching principal content in useEffect. Don’t utiliser it pour content vous vouloir ranked.

A reminder I garder coming back to: “Googlebot can render it” n’est pas the même as “vous devez faire Googlebot render it.” Rendering is expensive, deferred, and pas universal à travers robots d’exploration.

The Metadata API (App Router)

The Metadata API is Server Component seulement — metadata resolves on le serveur avant lune page renders, so it lands in the initial HTML. Export metadata from layout.js or page.js:

export const metadata: Metadata = {
  title: 'My Page',
  description: 'Page description',
}

Or, quand the tags depend on récupéré données, utiliser generateMetadata():

export async function generateMetadata({ params }) {
  const post = await getPost(params.slug)
  return { title: post.title, description: post.description }
}

The fields que matter pour le SEO:

  • title — supports a string, a template ('%s | Brand'), a par défaut, and an absolute override. Définir the template une fois in the root layout and per-page titles inherit it.
  • description, alternates.canonical (the correct façon to définir a canonical in the App Router), openGraph (images doit resolve to absolute URLs), twitter (aussi utilisé by LinkedIn and Slack previews), and robots (index/follow plus googleBot-specific directives comme max-snippet, max-image-preview).
  • metadataBaserequis pour canonical and OG image URLs to resolve correctement. Forgetting it is the unique la plupart courant Suivant.js metadata bug: relative URLs leak into votre canonical and Ouvrir Graph tags, breaking social previews and muddying canonical signals.

A title-template exemple:

// app/layout.tsx
export const metadata: Metadata = {
  metadataBase: new URL('https://example.com'),
  title: { template: '%s | Brand Name', default: 'Brand Name' },
}
// app/blog/page.tsx
export const metadata: Metadata = { title: 'My Blog Post' }
// Output: <title>My Blog Post | Brand Name</title>

Two gotchas. Premier, metadata is shallowly merged from layout to page — a nested object comme openGraph défini in a child segment replaces the parent’s entirely, so une page-level openGraph: { title: 'Home' } quietly drops quelconque openGraph.images définir in the layout. Second — and ce un bites personnes — metadata seulement fonctionne in Server Components. Export it from a 'use client' fichier and it silently ne fait pashing.

Streaming metadata. Pour dynamically rendered pages, generateMetadata peut stream the metadata après the initial HTML. Googlebot executes JavaScript and inspects the complet DOM, so streamed metadata fonctionne pour Google. But Suivant.js detects “HTML-limited bots” — Bingbot, Twitterbot, Slackbot, facebookexternalhit — and ships les blocking metadata in the <head> à la place. Per the Suivant.js docs, “streaming metadata is disabled pour bots and robots d’exploration que expect metadata to be in the <head> tag.” Ce is automatic; aucun configuration nécessaire. It’s a detail almost aucun competing guide covers, and it’s pourquoi streaming metadata isn’t a risk pour the bots que can’t wait pour it. Prerendered pages are a différent cas entirely — metadata là resolves at construire temps, so there’s aucun stream to worry à propos de. Ce behavior is version-specific (current as of Suivant.js 16.2.10); re-check the generateMetadata docs quand vous upgrade. And parce que delivery paths differ by entry point, vérifier metadata two façons, pas un: a direct/production requête (curl -I or View Source) and a client-side navigation to the même route — the head peut mettre à jour differently entre the two.

Evidence for this claim Prerendered Next.js pages do not use streaming metadata because metadata is resolved at build time in the documented path. Scope: route output, metadata and deployment Confidence: high · Verified: Metadata and OG images

Metadata with suivant/head (Pages Router)

On lune pages Router, metadata lives in <Head> from next/head:

import Head from 'next/head'

export default function Page() {
  return (
    <>
      <Head>
        <title>My Page | Brand</title>
        <meta name="description" content="Description" />
        <link rel="canonical" href="https://example.com/my-page" />
      </Head>
      {/* page content */}
    </>
  )
}

Définir title and description par page (pas simplement in _app.js), and put a canonical on every page notamment paginated variants. The next-seo package standardizes ce with a <NextSeo> component and structured-data helpers. Migrating to the App Router mostly signifie trading next/head and next-seo pour the native metadata export.

Do vous besoin the next-seo package? It’s a third-party plugin (pas partie of Suivant.js itself) — actively maintained, at v7.2,0 as of ce writing, pas archived. Its propre docs are explicit à propos de où it fits: pour standard meta tags on the App Router, the package’s README recommends en utilisant Suivant.js’s built-in generateMetadata/metadata export au lieu de <NextSeo>; on the Pages Router, <NextSeo> is encore a reasonable convenience couche over next/head. The un App Router utiliser cas the package encore covers is its JSON-LD helper components (ArticleJsonLd, FAQPageJsonLd, etc. with useAppDir), qui some teams préférer over hand-rolling <script type="application/ld+json">. Bottom line: on a nouveau App Router construire, reach pour the native Metadata API premier — the package is optional, pas a requirement, and its propre maintainers dire so.

Sitemaps

In the App Router, app/sitemap.ts is a fichier convention que outputs /sitemap.xml:

import type { MetadataRoute } from 'next'

export default function sitemap(): MetadataRoute.Sitemap {
  return [
    { url: 'https://acme.com', lastModified: new Date(), priority: 1 },
    { url: 'https://acme.com/blog', lastModified: new Date(), priority: 0.8 },
  ]
}

Pour grand sites, generateSitemaps() shards into multiple fichiers (Google’s limite is 50 000 URLs per sitemap), chaque served at /.../sitemap/[id].xml. Le sitemap output aussi supports image sitemaps, video sitemaps, and localized alternates.languages. On the Pages Router, utiliser next-sitemap or generate pages/sitemap.xml.js with getServerSideProps.

robots.txt

app/robots.ts generates votre robots fichier programmatically:

export default function robots(): MetadataRoute.Robots {
  return {
    rules: [{ userAgent: '*', allow: '/', disallow: '/private/' }],
    sitemap: 'https://acme.com/sitemap.xml',
  }
}

Per-user-agent rules and multiple sitemaps are pris en charge. Lune pages Router uses a static public/robots.txt. The rule vous pouveznot obtenir incorrect on soit router: jamais disallow votre JavaScript or CSS — Google won’t render from blocked fichiers, and on a JS framework que peut blank out lune page entirely.

suivant/image and Core Web Vitals

next/image is un of the strongest raisons to utiliser the framework pour le SEO. It lazy-loads below-the-fold images, exige width/height (or fill) so it reserves space and empêche layout shift / CLS, sert WebP/AVIF automatically, and emits a proper srcset from the sizes prop. The unique la plupart important CWV optimization is the priority prop on votre hero / above-the-fold image, qui preloads it pour a faster LCP:

<Image src="/hero.jpg" width={1200} height={630} priority alt="Hero" />

Forgetting priority on the LCP image is the la plupart courant Suivant.js CWV mistake — and CWV problems are widespread on réel Suivant.js sites (voir the Stats tab pour the Salt Agency données). alt is requis: vide pour decorative images, descriptive pour content images.

suivant/lien and maillage interne

next/link renders standard <a href> anchors in the HTML, so Google follows les normally, and it adds client-side navigation plus background prefetching of in-viewport liens in production. The SEO rule is simple: utiliser next/link pour lien internes, and jamais substitute an onClick handler or JavaScript navigation que doesn’t produce a réel anchor — ceux liens aren’t crawlable. Utiliser prefetch={false} on low-value liens to enregistrer bandwidth si vous devez.

Dynamic routes and generateStaticParams

generateStaticParams() indique Suivant.js qui dynamic routes to pre-render at construire temps:

// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
  const posts = await getPosts()
  return posts.map((post) => ({ slug: post.slug }))
}

Pages construit ce façon are entièrement static HTML — meilleur pour le SEO. Sans it, dynamic routes are rendered on-demand (SSR) by par défaut, qui is fine but reintroduces server latency. Combine it with revalidate (ISR) pour content que updates regularly. Assurez-vous tout important dynamic URLs are in generateStaticParams so nothing waits on the render queue.

Données structurées (JSON-LD)

The Metadata API has aucun structured-data field — vous inject JSON-LD as a <script> in a Server Component, qui garde it in le serveur-rendered HTML at zero client-bundle cost:

const jsonLd = { '@context': 'https://schema.org', '@type': 'Article', /* … */ }
return <script type="application/ld+json"
  dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />

Article/BlogPosting, BreadcrumbList, Product, and FAQPage are the usual types. Validate with the Résultats enrichis Tester après quelconque rendering modifier.

Courant Suivant.js SEO mistakes

Drawn from réel audits and the patterns ci-dessus:

  1. Manquant or relative canonicals — souvent from a forgotten metadataBase, qui aussi breaks OG image URLs.
  2. Aucun priority on the LCP image — the biggest CWV miss.
  3. 404 views returning 200 — utiliser the built-in notFound() to retourner a réel status; soft 404s are rampant on Suivant.js sites.
  4. metadata exported from a Client Component — silently ne fait pashing; it’s Server Component seulement.
  5. CSR pour principal content — fetching critical content in useEffect signifie non-Google robots d’exploration obtenir vide pages.
  6. Hash (#) routing au lieu de the History API — ceux views aren’t separately crawlable.
  7. Pas exporting generateStaticParams — dynamic routes render on-demand au lieu de being pre-built.
  8. openGraph overwritten by layout inheritance — child segments replace, pas merge.
  9. Blocking JS/CSS in robots.txt or via a Content Security Policy que arrête Googlebot’s headless Chrome from chargement scripts — tester with Inspection d’URL.

Deployment notes

Suivant.js is construit by Vercel; hosting là donne tight integration (edge CDN pour static and ISR pages, bon TTFB) but isn’t requis. Define redirection permanentes in redirects() in next.config.js (renvoie 308, or 301 with permanent: true) pour reliable signaling to robots d’exploration, définir security and mise en cache headers via headers(), and utiliser X-Robots-Tag réponse headers pour path-based noindex rules quand per-page robots metadata is awkward.

Ce que to vérifier où. Cache state, code d’états, redirections, and streamed metadata don’t tout montrer up in the même tester — a route peut regarder fine in un vérifier and encore be broken in un autre:

VérifierOù to regarderPourquoi it peut differ from ce que vous voir rendered
Cache/revalidation ageRéponse headers on a direct requête (curl -I)ISR peut serve a stale page on la requête correct après the window expires
Direct HTTP statuscurl -I on the production URL, pas the rendered UIA “not found” view sans notFound() encore renvoie 200
Redirection behaviorThe réel context que fires it — redirect() in a Server Action, a Route Handler, vs. a client onClickCode d’état and réponse chemin differ by invocation context, pas simplement the destination
Streamed metadataDirect requête and client-side navigation to the même routeOrdinary clients peut obtenir streamed metadata; HTML-limited bots obtenir blocking metadata; the two paths aren’t identical
Client-side transitionsNavigate in-app, alors re-check the <head>A route that’s correct on premier charger peut drift après a client transition

None of ce is guaranteed by the framework — Suivant.js donne vous the mechanisms (redirects(), notFound(), revalidation, streaming), but cache keys, invalidation, preview state, and deployment configuration are encore votre responsibility to obtenir correct and to tester in production, pas simplement locally.

Un dernier remarque on dynamic rendering — serving prerendered HTML to bots and JavaScript to utilisateurs. Google has deprecated it as a recommendation: “dynamic rendering was a workaround and pas a long-term solution.” Vous don’t besoin it on Suivant.js anyway — SSR, SSG, ISR, and Server Components tout put content in the HTML natively. Mention it so vous recognize it in an audit; don’t construire on it.

The happy chemin: App Router + Server Components + ISR + the Metadata API (with metadataBase) + next/image with priority. Obtenir ceux correct and la plupart of Suivant.js SEO is handled.

Add an expert note

Pin an expert quote

New person? Create their unclaimed profile at /admin/experts/ → Pin a quote first.