SEO Architecture for Next.js E-commerce
SEO Architecture for Next.js E-commerceMarch 18, 2026

SEO Architecture for Next.js E-commerce

How we moved a storefront from a Lighthouse SEO score of 67 to 100: SSR, semantic HTML, metadata, crawlability, and the mistakes that keep product pages invisible.

Table of Contents

  1. SEO Is an Architecture Problem
  2. SSR and Indexable HTML
  3. Metadata That Search Engines Can Use
  4. Semantic HTML and Alt Text
  5. Crawl Budget and Faceted URLs
  6. Measuring the Uplift

SEO Is an Architecture Problem {#architecture}

On the legacy Vue 2 app, product content lived behind client rendering. Crawlers got a shell. Lighthouse SEO sat at 67: missing descriptions, missing alts, and pages that were hard to crawl.

After the Next.js rebuild we hit 100 on Lighthouse SEO for key templates. Projected organic visibility uplift was 30–40%. The work was not keyword stuffing. It was making the HTML tell the truth.

SSR and Indexable HTML {#ssr}

If the title, price, and description are not in the first HTML, you are negotiating with crawlers.

// app/products/[slug]/page.tsx
export default async function ProductPage({ params }: { params: { slug: string } }) {
  const product = await getProduct(params.slug);

  return (
    <article>
      <h1>{product.name}</h1>
      <p>{product.shortDescription}</p>
      <AddToCart productId={product.id} />
    </article>
  );
}

Client widgets (add to cart, reviews) can hydrate. The copy that should rank cannot wait for JavaScript.

SSG/ISR for catalog pages is even better: fast TTFB and stable HTML for crawlers.

Metadata That Search Engines Can Use {#metadata}

import type { Metadata } from 'next';

export async function generateMetadata({
  params,
}: {
  params: { slug: string };
}): Promise<Metadata> {
  const product = await getProduct(params.slug);

  return {
    title: `${product.name} | Lab Store`,
    description: product.metaDescription,
    alternates: { canonical: `https://example.com/products/${product.slug}` },
    openGraph: {
      title: product.name,
      description: product.metaDescription,
      images: [{ url: product.ogImage, width: 1200, height: 630 }],
    },
  };
}

Rules we enforced in CMS validation:

  • Unique title and description per indexable URL
  • Canonical for every product and article
  • No empty description shipping to production

Missing meta descriptions were a large chunk of the old 67 score. They are also the easiest fix once metadata is generated on the server.

Semantic HTML and Alt Text {#html}

Lighthouse flags what users with assistive tech also need.

<header>
  <nav aria-label="Primary">...</nav>
</header>
<main>
  <article>
    <h1>{product.name}</h1>
    <figure>
      <Image src={product.hero} alt={product.heroAlt} width={1200} height={800} />
      <figcaption>{product.name} product photo</figcaption>
    </figure>
  </article>
</main>
  • One h1 per template
  • Buttons are buttons; links are links
  • Every meaningful image has alt; decorative images are alt=""
  • Tables for lab result data, not stacked divs pretending to be tables

This doubled as an accessibility pass. SEO and a11y often fail for the same structural reasons.

Crawl Budget and Faceted URLs {#crawl}

E-commerce search creates infinite URLs: ?color=red&sort=price.

const indexable = new Set(['page']);

export function robotsForSearch(searchParams: URLSearchParams) {
  const extra = [...searchParams.keys()].filter((key) => !indexable.has(key));
  return extra.length ? { robots: { index: false, follow: true } } : {};
}

Also:

  • XML sitemap for products that are in stock and active
  • robots.txt that does not block /_next static assets needed to render
  • 301 map from the Vue routes so equity is not dumped on 404s

Measuring the Uplift {#measure}

Lighthouse 100 is a hygiene score. Organic lift needs Search Console:

  • Index coverage for /products/*
  • Query impressions for brand + test-type keywords
  • Core Web Vitals (SEO and performance are coupled in ranking systems)

We treated SEO as a regression suite: CI failed if a product template shipped without title, description, canonical, or h1. That is how 67 became 100 and stayed there.