DEV Community

ENZORA
ENZORA

Posted on Originally published at enzora.vercel.app

Next.js App Router SEO checklist: metadata, sitemap.ts, robots.ts, JSON-LD

Search engines still reward boring technical hygiene. App Router makes the right things easier—central metadata, file-based sitemap and robots—but only if you wire them consistently across routes. This checklist reflects how we keep a multi-page agency site indexable on a single Vercel project (enzora.vercel.app), without buying SEO plugins or duplicate WordPress stacks.

1. One canonical host

Pick one HTTPS origin and use it everywhere: metadataBase, canonical tags, JSON-LD url fields, and sitemap.xml locs. We read NEXT_PUBLIC_SITE_URL with a default of https://enzora.vercel.app so previews can override without forked code.

Helper:

export const siteUrl = (process.env.NEXT_PUBLIC_SITE_URL ?? "https://enzora.vercel.app").replace(/\/$/, "");
Enter fullscreen mode Exit fullscreen mode

Every indexable route should export alternates.canonical as an absolute URL on that host, not a relative path alone. Relative canonicals break when content is scraped from staging.

2. Metadata API per route

Layout defaults

app/layout.tsx sets site-wide title template, description, Open Graph defaults, icons, and Search Console verification. Keep verification dual-path:

  • HTML file in /public/google*.html (legacy but reliable).
  • metadata.verification.google fed by NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION for tag-based verification.

Page-level overrides

Each marketing route exports metadata or generateMetadata:

  • Title ~50–60 characters, primary keyword near the front.
  • Description ~150–160 characters, honest click-worthy copy (no fake guarantees).
  • openGraph title/description/url aligned with canonical.
  • twitter card set to summary_large_image when OG images exist.

Dynamic segments (services/[slug], blog/[slug]) should compute metadata from a single content source—do not hand-edit fifty copies.

3. Exactly one H1, intentional headings

App Router does not police heading order; designers do. Each page gets one H1 matching search intent. Section titles use H2/H3. Avoid duplicating the site name in every H1 unless the page is the home page.

For service hubs, pair the H1 with a short intro paragraph that mentions the primary keyword naturally in the first 100 words, then link internally to related hubs (web design → ecommerce → SEO).

4. sitemap.ts that mirrors reality

Use app/sitemap.ts, not a static public/sitemap.xml that rots. Build locs from:

  • Static marketing paths (home, services index, packages, legal).
  • All service detail slugs you actually ship (including hub routes like /services/web-design).
  • Blog posts from your data layer.
  • Location/market pages if you have them.

Set sensible changeFrequency and priority, but remember Google largely ignores those fields—they are still useful for your own audits.

When you add a hub route, add it to the generator the same day. CI can fetch /sitemap.xml and assert critical URLs exist.

5. robots.ts with explicit disallows

app/robots.ts should:

  • Allow / for normal crawling.
  • Point to the absolute sitemap URL on your canonical host.
  • Disallow non-content paths: /api/, /_next/, preview or demo trees you mark noindex (we disallow /work static demos that are intentionally non-indexable).

Do not block CSS or JS—Google needs assets to render pages.

6. Redirects instead of duplicate URLs

When consolidating service slugs (for example /services/online-stores → /services/ecommerce), implement 301 redirects in next.config.ts. Update internal links and the sitemap to the canonical slug only. Keep redirects for at least a year; search engines are slow to forget old URLs.

7. Open Graph images

app/opengraph-image.tsx at the root covers defaults. Dynamic segments can expose opengraph-image.tsx with generateStaticParams for high-value routes (services hubs, blog posts). Keep layouts simple—Satori fails on complex nested flex unless every multi-child div sets display: "flex".

8. JSON-LD with stable @ids

Structured data should match visible content:

  • Organization / ProfessionalService sitewide with legal name, phone, email, postal address, and sameAs social URLs you actually operate—never a similarly named third-party LinkedIn page.
  • FAQPage generated from the same FAQ array rendered on the page (home FAQ ≠ services FAQ ≠ full FAQ page—each schema matches its visible list).
  • BlogPosting on articles with datePublished, headline, and publisher linked to the organization @id.
  • BreadcrumbList on nested pages.

Avoid stuffing schema that is not on the page; rich-result spam invites manual actions.

9. Internal and outbound links

Internal links carry relevance and crawl paths: services ↔ blog ↔ contact. Outbound links to authoritative docs (Google Search Central, web.dev, MDN) signal E-E-A-T when used sparingly and in context—not as a footer link farm.

10. Search Console readiness (human step)

Automated agents cannot click “Submit sitemap” for you. Before launch, verify:

  • Property for https://enzora.vercel.app (domain or URL prefix).
  • Ownership via HTML file or meta tag env var.
  • /sitemap.xml returns 200 with only canonical URLs.
  • No accidental noindex on money pages.

After deploy, manually submit the sitemap and monitor Coverage for excluded /work or /api paths—those should be intentional.

11. Launch regression checks

Run this on every release candidate:

  1. Fetch /robots.txt and /sitemap.xml on production.
  2. Spot-check five random service pages for canonical + H1 + FAQ schema parity.
  3. Mobile Lighthouse SEO category ≥ 90 (not a ranking factor, but catches missing meta/titles).
  4. Link crawl script against sitemap locs (we use a small Node fetch loop in CI optional).

Closing thought

App Router SEO is mostly routing discipline: one hostname, one slug per intent, metadata generated from the same source as the UI, and sitemap/robots that update when routes do. Fancy content strategies fail when /services/old-name still resolves without a redirect. Fix the plumbing first; the rankings fight happens later, honestly, on the content and product—not on trick metadata.


Originally published on the ENZORA blog. ENZORA builds fast Next.js and Shopify sites, SEO and ads for growing businesses: enzora.vercel.app.

Top comments (0)