← All framework guides

Next.js SEO: 11 checks and how to fix them

These are the Next.js-specific checks RankCLI runs, and the fix for each one in Next.js's own idiom. Next.js renders on the server by default, which removes a whole class of SEO problems before you start.

11 checks
meta-framework
SSR by default

Run all of these against your own site, free and without an account:

npx @rankcli/cli audit -u https://your-site.dev

What Next.js gives you for free

  • •Server-side rendering by default
  • •Static site generation support
  • •Built-in Image component with optimization
  • •Automatic code splitting
  • •File-based routing
  • •Built-in metadata API (App Router)

The checks

warning
NEXTJS_NO_METADATA_API

Not using Next.js Metadata API

Next.js 13+ has a built-in Metadata API that's better than react-helmet.

Use the Metadata API in your layout or page:

// app/page.tsx
import { Metadata } from 'next';

export const metadata: Metadata = {
  title: 'Page Title',
  description: 'Page description',
  openGraph: {
    title: 'OG Title',
    description: 'OG Description',
    images: ['/og-image.jpg'],
  },
};

// For dynamic metadata
export async function generateMetadata({ params }): Promise<Metadata> {
  const product = await getProduct(params.id);
  return {
    title: product.name,
    description: product.description,
  };
}
warning
NEXTJS_GENERATESTATICPARAMS_MISSING

Dynamic routes without generateStaticParams

Dynamic routes like [id] should pre-generate static pages for SEO.

Add generateStaticParams to pre-render dynamic pages:

// app/products/[id]/page.tsx
export async function generateStaticParams() {
  const products = await getProducts();
  return products.map((product) => ({
    id: product.id.toString(),
  }));
}
warning
NEXTJS_NO_GETSTATICPROPS

Pages without getStaticProps/getServerSideProps

Pages fetching data client-side miss SEO benefits of SSR/SSG.

Move data fetching to getStaticProps (for static) or getServerSideProps (for dynamic):

// pages/products.tsx
export async function getStaticProps() {
  const products = await fetchProducts();
  return {
    props: { products },
    revalidate: 3600, // ISR: regenerate every hour
  };
}
warning
NEXTJS_IMAGE_NOT_USED

Using <img> instead of next/image

The next/image component provides automatic optimization, lazy loading, and prevents CLS.

Replace <img> with next/image:

import Image from 'next/image';

// Before
<img src="/hero.jpg" alt="Hero" />

// After
<Image
  src="/hero.jpg"
  alt="Hero"
  width={1200}
  height={630}
  priority // for above-the-fold images
/>
notice
NEXTJS_NO_PRIORITY_IMAGE

LCP image missing priority prop

The largest image above the fold should have priority={true} for faster LCP.

Add priority to your hero/LCP image:

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

Missing robots.txt configuration

Next.js can generate robots.txt automatically.

App Router - create app/robots.ts:

import { MetadataRoute } from 'next';

export default function robots(): MetadataRoute.Robots {
  return {
    rules: {
      userAgent: '*',
      allow: '/',
      disallow: '/api/',
    },
    sitemap: 'https://yoursite.com/sitemap.xml',
  };
}

Pages Router - create public/robots.txt or use next-sitemap package.

warning
NEXTJS_NO_SITEMAP

Missing sitemap configuration

Next.js can generate sitemaps automatically.

App Router - create app/sitemap.ts:

import { MetadataRoute } from 'next';

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const products = await getProducts();

  return [
    { url: 'https://yoursite.com', lastModified: new Date() },
    { url: 'https://yoursite.com/about', lastModified: new Date() },
    ...products.map((product) => ({
      url: `https://yoursite.com/products/${product.id}`,
      lastModified: product.updatedAt,
    })),
  ];
}
warning
NEXTJS_USE_CLIENT_OVERUSE

Overusing 'use client' directive

Too many client components reduces SSR benefits.

Keep 'use client' at the leaf level:

// Bad - entire page is client-rendered
'use client';
export default function Page() { ... }

// Good - only interactive parts are client
// page.tsx (Server Component)
import ClientButton from './ClientButton';
export default function Page() {
  return <div>
    <h1>Server rendered content</h1>
    <ClientButton /> {/* Only this is client */}
  </div>
}
notice
NEXTJS_FONT_NOT_OPTIMIZED

Not using next/font for fonts

next/font automatically optimizes fonts and prevents CLS.

Use next/font for Google Fonts or local fonts:

import { Inter } from 'next/font/google';

const inter = Inter({ subsets: ['latin'] });

export default function RootLayout({ children }) {
  return (
    <html lang="en" className={inter.className}>
      <body>{children}</body>
    </html>
  );
}
notice
NEXTJS_NO_TRAILING_SLASH_CONFIG

Inconsistent trailing slash handling

Inconsistent URLs can cause duplicate content issues.

Configure trailingSlash in next.config.js:

module.exports = {
  trailingSlash: true, // or false
};

Check your own Next.js site

280+ checks including every one above. No signup, nothing leaves your machine.

Run a free audit