How to Get Your Next.js App on Google (Step-by-Step)

Your Next.js app isn't showing up on Google? Here's exactly how to fix it — SSR config, meta tags, sitemaps, structured data, and the mistakes most developers make.

How to Get Your Next.js App on Google (Step-by-Step)

You deployed your Next.js app. You waited. You Googled your site name. Nothing.

Next.js is one of the most SEO-friendly frameworks out there — but it doesn't mean your app is automatically indexed. There are specific things you need to configure, and if you miss any of them, Google either can't find your pages, can't read them, or doesn't think they're worth ranking.

Here are 6 steps to get your Next.js app properly indexed on Google, ordered by impact.

Step 1: Make Sure You're Actually Using SSR or SSG

Why this matters: Next.js supports multiple rendering modes — SSR, SSG, ISR, and client-side rendering. If your pages use 'use client' at the top and fetch all data in useEffect, you're essentially building a client-rendered SPA. Google receives a loading spinner instead of content.

Check your rendering mode:

curl -s https://your-domain.com | head -100

If you see your actual page content (headings, text, meta tags) in the HTML, you're good. If you see an empty <div id="__next"></div> with no content, your pages are client-rendered.

How to fix it — use Server Components (App Router) or getServerSideProps (Pages Router):

App Router (recommended):

// app/products/page.tsx
// Server Component by default — no 'use client' needed
export default async function ProductsPage() {
  const products = await fetch('https://api.example.com/products').then(r => r.json());

  return (
    <main>
      <h1>Our Products</h1>
      {products.map((p: any) => (
        <div key={p.id}>
          <h2>{p.name}</h2>
          <p>{p.description}</p>
        </div>
      ))}
    </main>
  );
}

Pages Router:

// pages/products.tsx
export async function getServerSideProps() {
  const res = await fetch('https://api.example.com/products');
  const products = await res.json();
  return { props: { products } };
}

export default function ProductsPage({ products }: { products: any[] }) {
  return (
    <main>
      <h1>Our Products</h1>
      {products.map((p) => (
        <div key={p.id}>
          <h2>{p.name}</h2>
          <p>{p.description}</p>
        </div>
      ))}
    </main>
  );
}

For static pages that rarely change, use generateStaticParams (App Router) or getStaticProps (Pages Router) instead. Google can index these even faster because they're pre-built HTML files.

Step 2: Add Unique Meta Tags to Every Page

Why this matters: Without a unique title and description on each page, Google either generates its own snippet (usually bad) or treats your pages as duplicates.

App Router — use the metadata export:

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

export const metadata: Metadata = {
  title: 'Our Products | Your App',
  description: 'Browse our full product catalog with pricing and reviews.',
  alternates: {
    canonical: 'https://your-domain.com/products',
  },
  openGraph: {
    title: 'Our Products | Your App',
    description: 'Browse our full product catalog with pricing and reviews.',
    url: 'https://your-domain.com/products',
    images: ['https://your-domain.com/og-products.jpg'],
  },
};

export default function ProductsPage() {
  // ...
}

For dynamic pages, use generateMetadata:

// app/products/[id]/page.tsx
import type { Metadata } from 'next';

export async function generateMetadata({ params }: { params: { id: string } }): Promise<Metadata> {
  const product = await fetch(`https://api.example.com/products/${params.id}`).then(r => r.json());

  return {
    title: `${product.name} | Your App`,
    description: product.description.slice(0, 160),
    alternates: {
      canonical: `https://your-domain.com/products/${params.id}`,
    },
  };
}

Pages Router — use the Head component:

import Head from 'next/head';

export default function ProductsPage() {
  return (
    <>
      <Head>
        <title>Our Products | Your App</title>
        <meta name="description" content="Browse our full product catalog." />
        <link rel="canonical" href="https://your-domain.com/products" />
      </Head>
      <main>{/* ... */}</main>
    </>
  );
}

Step 3: Create and Submit a Sitemap

Why this matters: A sitemap tells Google every URL on your site. Without one, Google relies solely on link discovery — if a page isn't linked from anywhere, it won't be found.

Next.js App Router has built-in sitemap support:

// app/sitemap.ts
import type { MetadataRoute } from 'next';

export default function sitemap(): MetadataRoute.Sitemap {
  return [
    {
      url: 'https://your-domain.com',
      lastModified: new Date(),
      changeFrequency: 'weekly',
      priority: 1,
    },
    {
      url: 'https://your-domain.com/products',
      lastModified: new Date(),
      changeFrequency: 'weekly',
      priority: 0.9,
    },
    {
      url: 'https://your-domain.com/about',
      lastModified: new Date(),
      changeFrequency: 'monthly',
      priority: 0.5,
    },
  ];
}

For dynamic routes, fetch your data:

// app/sitemap.ts
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const products = await fetch('https://api.example.com/products').then(r => r.json());

  const productUrls = products.map((p: any) => ({
    url: `https://your-domain.com/products/${p.id}`,
    lastModified: new Date(p.updatedAt),
    changeFrequency: 'weekly' as const,
    priority: 0.8,
  }));

  return [
    { url: 'https://your-domain.com', lastModified: new Date(), priority: 1 },
    ...productUrls,
  ];
}

This generates a sitemap.xml at https://your-domain.com/sitemap.xml. Submit it in Google Search Console.

Also add a robots.txt:

// app/robots.ts
import type { MetadataRoute } from 'next';

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

Step 4: Use Proper Link Elements for Internal Navigation

Why this matters: Googlebot follows <a href> links to discover pages. If you use onClick handlers with router.push(), there's no link in the HTML for Google to follow.

Before (invisible to Google):

<div onClick={() => router.push('/products')}>View Products</div>

After (crawlable by Google):

import Link from 'next/link';

<Link href="/products">View Products</Link>

Next.js's Link component renders a standard <a> tag that Googlebot can follow. It also prefetches the page for faster user navigation.

Step 5: Add JSON-LD Structured Data

Why this matters: Structured data qualifies your pages for rich results in Google — FAQ dropdowns, product cards, breadcrumbs, how-to steps. These get significantly more clicks than standard search results.

App Router approach:

// app/products/[id]/page.tsx
export default async function ProductPage({ params }: { params: { id: string } }) {
  const product = await fetch(`https://api.example.com/products/${params.id}`).then(r => r.json());

  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'Product',
    name: product.name,
    description: product.description,
    image: product.image,
    offers: {
      '@type': 'Offer',
      price: product.price,
      priceCurrency: 'USD',
    },
  };

  return (
    <>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />
      <main>
        <h1>{product.name}</h1>
        <p>{product.description}</p>
      </main>
    </>
  );
}

Validate your structured data with Google's Rich Results Test after deployment.

Step 6: Optimize Core Web Vitals

Why this matters: Google uses Core Web Vitals (LCP, INP, CLS) as ranking signals. A slow or janky page ranks lower than a fast one with identical content.

Quick wins for Next.js:

// Use next/image for automatic optimization
import Image from 'next/image';

<Image
  src="/hero.jpg"
  alt="Hero image"
  width={1200}
  height={630}
  priority  // Loads above-the-fold images immediately
/>
// Use next/font to eliminate font layout shift
import { Inter } from 'next/font/google';

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

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

Check your scores: Run your URL through Google PageSpeed Insights. Fix anything in the red or orange zone.

Common Mistakes to Avoid

Fetching data entirely on the client side: If your page shows a loading spinner while data loads, Google sees the spinner, not your content. Fetch data on the server using Server Components, getServerSideProps, or getStaticProps.

Using 'use client' on pages that don't need it: In the App Router, components are Server Components by default. Only add 'use client' to components that need browser APIs (event handlers, useState, useEffect). Keep your page-level components as Server Components.

Blocking Googlebot in robots.txt: Check your robots.txt at https://your-domain.com/robots.txt. Make sure it doesn't disallow the paths you want indexed.

No canonical URLs: Without canonical tags, Google may index multiple URL variations (with/without trailing slash, with query params) as separate pages, splitting your ranking power.

Summary

Next.js gives you the tools to be SEO-friendly — but you have to use them. Here's the checklist:

  1. Use SSR or SSG — don't client-render pages you want indexed
  2. Add unique metadata (title, description, canonical) to every page
  3. Generate a sitemap.ts and submit it to Google Search Console
  4. Use Link components instead of router.push() for navigation
  5. Add JSON-LD structured data for rich result eligibility
  6. Optimize images with next/image and fonts with next/font

Want to see how Google currently views your site? Scan What's Ranking to identify what's indexed and what's missing.

FAQ

How long does it take for Google to index a Next.js app?

After deploying SSR-enabled pages and submitting your sitemap, Google typically begins indexing within a few days to 2 weeks. You can request immediate indexing of specific URLs via Google Search Console's URL Inspection tool.

Should I use the App Router or Pages Router for SEO?

Both work well for SEO. The App Router (Next.js 13+) has built-in metadata exports, sitemap.ts, and robots.ts — making SEO configuration cleaner. The Pages Router requires the Head component and manual sitemap setup but is equally effective.

Does Vercel automatically handle SEO for Next.js?

Vercel handles deployment and CDN, but SEO configuration is your responsibility. You still need to add meta tags, create sitemaps, configure structured data, and ensure your pages are server-rendered. Vercel doesn't add these automatically.

Why are my Next.js pages indexed but showing wrong titles in Google?

Google may use its own title if your <title> tag is missing, duplicated across pages, or doesn't match the page content. Ensure every page has a unique, descriptive title set via the metadata export or Head component.

Do I need SSR for a Next.js blog?

No — use Static Site Generation (SSG) instead. Blog posts don't change on every request, so pre-rendering them at build time with generateStaticParams (App Router) or getStaticPaths + getStaticProps (Pages Router) is faster and more SEO-friendly than SSR.