Programmatic SEO·April 28, 2026·10 min read

How to Build Programmatic Pages in Next.js

Next.js is well-suited for programmatic SEO at scale, but only if you configure ISR, generateStaticParams, generateMetadata, canonical tags, and sitemap generation correctly. This guide covers the complete Next.js programmatic SEO setup from routing to robots.txt, including the canonical tag mistake that silently hurts thousands of pages.

Next.js is the most popular framework for programmatic SEO builds among developers who are also thinking about performance and indexation. The App Router's support for Incremental Static Regeneration, static parameter pre-building, per-page metadata generation, and programmatic sitemap generation makes it a strong fit for large page sets. But the framework has enough moving parts that a misconfiguration - particularly around canonical tags - can silently produce SEO problems across your entire page set. This guide covers the complete setup.

Verify canonical tags on your programmatic pages

Before scaling to hundreds of pages, confirm that your canonical tag setup is correct on a sample of your programmatic URLs. The canonical tag on each programmatic page should point to that page's own URL (self-referencing canonical) - not to a base URL or a parameterized variant. Use the Canonical Tag Checker to verify the canonical configuration on any URL in your set.

Try it inline

Canonical Tag Checker

Verify that each programmatic page has a correct self-referencing canonical tag before scaling.

Open full tool
Loading tool…

ISR: the right rendering strategy for programmatic pages

Incremental Static Regeneration (ISR) is the recommended rendering strategy for most programmatic SEO page sets in Next.js. ISR generates pages statically at build time (or on first request) and then revalidates them in the background at a configured interval. This gives you the performance benefits of static pages - fast TTFB, CDN cacheability, no server round-trip per request - while allowing data to stay fresh without a full rebuild.

Configure ISR with the revalidate export in your route segment:

// app/[modifier]/page.tsx
export const revalidate = 86400; // revalidate every 24 hours

Choose your revalidate interval based on your data freshness tier. For slowly-changing data (product specs, geographic data), 24-72 hours is appropriate. For time-sensitive data (pricing, availability), use a shorter interval of 1-6 hours, or consider switching to server-side rendering for pages where real-time accuracy is critical.

generateStaticParams: pre-building your priority pages

generateStaticParams tells Next.js which parameter values to pre-build at deploy time. For a large programmatic set, you typically do not want to pre-build all pages at deploy time - that would make every deploy slow. Instead, use generateStaticParams to pre-build your highest-priority pages (highest search volume, core modifier values) and let the remaining pages be built on first request with ISR.

export async function generateStaticParams() {
  // Pre-build the top 200 modifier values by search volume
  const topModifiers = await getTopModifiers(200);
  return topModifiers.map((modifier) => ({ slug: modifier.slug }));
}

Set dynamicParams = true (the default) to allow on-demand generation for parameters not returned by generateStaticParams. This means a user who visits a long-tail modifier page for the first time will get the page generated on that first request and cached for subsequent visitors.

generateMetadata: unique metadata per page

Every programmatic page must have unique metadata - a distinct title and meta description generated from the page's specific data, not from a static template. Use the generateMetadata function to generate per-page metadata dynamically:

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const data = await getModifierData(params.slug);
  return {
    title: buildTitle(data),
    description: buildDescription(data),
    alternates: {
      canonical: \`https://yourdomain.com/\${params.slug}\`,
    },
  };
}

The alternates.canonical field in Next.js metadata is the correct way to set the canonical tag. It generates a <link rel="canonical"> tag in the page head. Set it to the page's own absolute URL (self-referencing canonical) on every programmatic page.

Validate your sitemap includes all programmatic pages

After generating your XML sitemap, use the XML Sitemap Checker to verify that your programmatic page URLs are included and that the sitemap is well-formed. A sitemap generation bug that omits your programmatic pages will slow indexation significantly - Google discovers pages faster through sitemaps than through crawling alone, especially for large sets where internal linking may not yet cover every page.

Try it inline

XML Sitemap Checker

Verify that your programmatic pages are included correctly in the generated sitemap.

Open full tool
Loading tool…

XML sitemap generation at scale

For large programmatic sets, generate your sitemap programmatically using Next.js's sitemap.ts file. For very large sets (thousands of pages), split into per-segment sitemaps using a sitemap index:

// app/sitemap.ts - returns sitemap index for large sets
export default function sitemap(): MetadataRoute.Sitemap {
  const modifiers = getAllModifiers();
  return modifiers.map((modifier) => ({
    url: \`https://yourdomain.com/\${modifier.slug}\`,
    lastModified: modifier.updatedAt,
    changeFrequency: 'weekly',
    priority: modifier.priority,
  }));
}

Set priority values based on your modifier importance: 0.8 for top-tier modifiers, 0.6 for mid-tier, 0.4 for long-tail. This signals to Google which pages to crawl first, which is important when your crawl budget is spread across a large page set.

Handling 404 for missing data

When a programmatic page is requested for a modifier value that does not exist in your data, return a proper 404. In Next.js App Router, call notFound() from the next/navigation package inside your page component when data is not found. Do not return a page with empty sections or "data not available" messages - those generate soft 404 signals that can accumulate and affect your site quality score.

robots.txt for programmatic sets

Your robots.txt should allow crawling of your programmatic page URLs. If your programmatic pages are in a specific path segment (e.g. /tools/[slug] or /cities/[slug]), confirm that path is not blocked by a Disallow rule. Generate robots.txt programmatically using Next.js's robots.ts file if you need dynamic logic.

The biggest mistake: forgetting canonical tags on parameterized routes

The most common Next.js programmatic SEO mistake is deploying parameterized routes without explicit canonical tag configuration. When no canonical tag is set, Google infers the canonical URL. For parameterized routes - where the same page might be accessible via /tool/[slug], /tool/[slug]?ref=email, or /tool/[slug]?sort=alpha - this can result in Google canonicalizing a URL-with-parameters as the canonical version, splitting the page's link equity across parameter variants.

Every programmatic page needs an explicit self-referencing canonical pointing to the clean parameter-free URL. This is not the same as pointing all pages to a single base URL - that would tell Google all your pages are duplicates of the base. Each page needs its own canonical pointing to itself. Set this in generateMetadata via alternates.canonical and verify it with the Canonical Tag Checker before launch.

What the Next.js programmatic SEO setup checklist looks like

  1. Configure ISR with a revalidate interval appropriate to your data freshness tier. Verify that ISR is working by checking that page timestamps update after the revalidation window.
  2. Implement generateStaticParams to pre-build your top 200-500 modifier values. Set dynamicParams = true to allow on-demand generation for the remainder.
  3. Implement generateMetadata to generate unique title, description, and canonical for every page. Set alternates.canonical to the page's own absolute URL on every programmatic page.
  4. Verify canonical tags on 10 sample pages using the Canonical Tag Checker before launch. Confirm every page has a self-referencing canonical, not a shared base URL canonical.
  5. Generate your sitemap using sitemap.ts. Validate it with the XML Sitemap Checker. Confirm all programmatic page URLs are included and the sitemap is well-formed.
  6. Confirm robots.txt allows crawling of your programmatic page path segment. Test by fetching your robots.txt and verifying no Disallow rules block the path.
  7. Test 404 handling by requesting a URL with a nonexistent modifier value. Confirm the server returns a true 404 status code, not a 200 with empty content.
  8. Deploy canary batch. Submit the sitemap to Google Search Console and monitor the Coverage report for indexation rate over the following weeks.
Checklist

Programmatic SEO in Next.js DOs & DON'Ts

DO

  • Use generateStaticParams to pre-build your highest-priority pages

    Pre-built pages are served instantly from CDN with no server processing. For your most important modifier combinations, pre-building at deploy time guarantees the fastest TTFB and prioritizes Googlebot's crawl of those pages.

  • Set a self-referencing canonical on every generated page via generateMetadata

    Use generateMetadata to return a canonical that includes the exact URL of the rendered page. Never rely on default or fallback canonicals for programmatic pages - each one needs an explicit declaration.

  • Generate a per-segment sitemap with sitemap.ts for each programmatic category

    A single sitemap with 50,000+ URLs is harder for Search Console to analyze than per-segment sitemaps that let you see indexation rates per modifier category. Use Next.js sitemap.ts to generate separate sitemaps per category.

  • Return 404 when the requested modifier combination has no data

    A programmatic page with no data should return a 404, not a 200 with empty fields. Use notFound() in your page component when the data fetch returns null for a given slug.

DON'T

  • Don't skip the self-referencing canonical on parameterized routes

    Next.js dynamic routes accessed via query strings or catch-all params can have multiple equivalent URLs. Without a canonical, Google may index the wrong variant or consolidate pages you intended to keep separate.

  • Don't put all programmatic pages in a single giant sitemap

    A sitemap that mixes programmatic pages with editorial content makes it impossible to isolate indexation problems by content type. Separate sitemaps let you see exactly which page set has quality issues.

  • Don't forget to add revalidate to your ISR pages

    Pages without a revalidate value are statically cached indefinitely. If your data changes (pricing, availability, rankings), set an appropriate revalidate interval so users and crawlers see fresh content.

  • Don't block your programmatic route pattern in robots.txt

    A well-intentioned robots.txt rule that blocks /category/* can accidentally block your entire programmatic page set. Test your robots.txt against your actual URL structure before deploying.

Free eBook

Grab The SEO Blueprint.

How to get found on Google, get cited by AI, and attract customers on autopilot - a practical guide for business owners and entrepreneurs.

  • Keyword research and on-page SEO tactics
  • Technical SEO and link building strategies
  • A 90-day SEO action plan

No spam. Unsubscribe any time. Your email is safe with us.

The SEO Blueprint - free eBook by Shammika Munugoda
Quick quiz · 5 questions

Programmatic SEO in Next.js - quick check

5 randomized questions drawn from a pool of 10. Different every time you take it. Takes about two minutes.

Next up in Programmatic SEO

Keep learning

More in Programmatic SEO

The Programmatic SEO Playbook: When and How to Scale Pages

7 min read

How to Find Programmatic SEO Opportunities

8 min read

How to Build Programmatic SEO Page Templates That Rank

9 min read

Skip the writing. Keep the SEO.

SEOGraphy drafts, illustrates, and publishes articles that follow the playbook above - automatically.

Try SEOGraphy free →