SiteFix.app
/
Start an audit

Next.js SEO audit

Check what a crawler actually receives from your Next.js deployment: per-route metadata, the generated sitemap and robots files, structured data, and the text that remains without JavaScript.

What has to be true for a Next.js page

Three things decide whether a Next.js route can be read: the metadata is produced for that route, the sitemap and robots files are actually served, and the main content exists in the HTML the server returns. A page that passes in the browser can still fail all three for a crawler that does not run scripts.

Metadata belongs to the route, not the layout

Titles and descriptions set once in the root layout apply to every route that does not override them, which produces duplicate titles across the site. In the App Router each route declares its own metadata, and a dynamic route uses generateMetadata so the values come from the actual record.

  • Export metadata or generateMetadata from the page, not only from the layout.
  • Set alternates.canonical explicitly; a self-referencing canonical is what the audit expects.
  • Declare translations with alternates.languages so a self reference and an x-default exist.
  • Provide openGraph.title, openGraph.description and an absolute openGraph.images URL.

What the audit reads from your deployment

The basic crawl fetches public HTML without executing scripts and records what arrived. Each finding links to the rule and the page evidence it was judged on.

  • Title and meta description length, plus duplicate titles across the pages read.
  • Canonical declarations from the head and the HTTP Link header.
  • robots meta and the X-Robots-Tag response header.
  • JSON-LD parsing, @context, declared types and whether they match visible content.
  • /sitemap.xml and /robots.txt, including whether robots.txt declares the sitemap.
  • Visible text with scripts removed, which is what a non-JavaScript crawler can read.
  • Open Graph and Twitter tags used for shared links.
  • A real request to the entry page as OAI-SearchBot, Claude-SearchBot and PerplexityBot.

Server components, client components and the empty shell

If the main text renders only inside a client component that depends on a browser API, the HTML response can be almost empty. Search crawlers may still render it; most AI crawlers will not. Keep the primary content in server components or static output, and reserve client components for interaction. The audit reports the visible text that survives without scripts so you can see the difference between your browser and a crawler.

A minimal App Router pattern

This pattern keeps metadata, canonical and social tags attached to the record that produced the page.

// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }) {
  const post = await getPost(params.slug);
  return {
    title: post.title,
    description: post.summary,
    alternates: {
      canonical: `/blog/${params.slug}`,
      languages: {
        'en-US': `/blog/${params.slug}`,
        'zh-CN': `/zh/blog/${params.slug}`,
      },
    },
    openGraph: {
      title: post.title,
      description: post.summary,
      images: ['https://your-domain.com/og/blog.png'],
    },
  };
}

What this audit cannot prove

A fetch does not prove Google indexed the page, and a canonical tag does not prove Google selected it. The basic crawl does not run JavaScript, so a client-rendered page is reported as unreadable rather than declared broken. Scores describe observed applicable rules only; they are not a ranking position or a count of AI citations.

Make the next fix a clear one.

Start with public-page evidence. Let your coding assistant find the right files, then verify the change.

Run a website audit