SEOIntermediatean afternoon

Get SEO right

Search engines and AI answer engines (Google AI Overviews, ChatGPT, Perplexity) drive most discovery, but they can only rank what they can crawl, understand, and load quickly. Getting SEO right means your pages actually show up when people search for what you built — instead of being invisible.

the approach

Stop thinking "keywords" and start thinking "ship the right HTML, fast, and prove it to crawlers." In 2026 the game is: server-render your content so bots see real text (not an empty div that hydrates later), give every page unique metadata and structured data, keep Core Web Vitals green, and let genuinely useful content do the ranking. Use your framework's native SEO conventions instead of hand-rolling <head> tags — in Next.js that's generateMetadata, sitemap.ts, robots.ts, and opengraph-image.tsx. The same server-rendered, well-structured HTML that ranks in Google is also what gets you cited by AI answer engines, so you build it once.

Step by step

  1. 1

    Server-render everything SEO-critical

    Content that must rank has to be in the initial HTML response, not fetched client-side in a useEffect. Use static generation (SSG/ISR) or server rendering for pages, tables, and text. Confirm by running curl on the URL — if you don't see your headline and body copy in the raw HTML, crawlers don't either.

  2. 2

    Give every page unique title, description, and canonical

    Use generateMetadata (or your framework's equivalent) so each page has a distinct title and meta description — no templated duplicates. Set metadataBase once in your root layout, then use relative canonical URLs per page to avoid duplicate-content self-competition (e.g. www vs non-www, trailing slashes, query params).

  3. 3

    Add structured data (JSON-LD)

    Inject a schema.org JSON-LD script matching each page type: Article/BlogPosting for posts, Product + Offer for shop items, Organization on your homepage, BreadcrumbList for nav. This is what powers rich results and helps AI engines understand your content. Validate every type with Google's Rich Results Test before shipping.

  4. 4

    Ship sitemap.xml and robots.txt

    Generate a sitemap listing all indexable URLs with lastModified dates (Next.js: app/sitemap.ts). Add robots.ts to allow crawling and point to the sitemap. Double-check no leftover noindex or Disallow: / from a staging config is blocking the whole site — this is the single most common way people accidentally deindex themselves.

  5. 5

    Generate real Open Graph images

    Social and AI previews need an og:image. Generate them dynamically per page (Next.js opengraph-image.tsx with next/og's ImageResponse) so every share shows the actual title, not a generic banner. Size them 1200x630.

  6. 6

    Get Core Web Vitals green

    Ranking and crawl budget both reward fast pages. Target good LCP (largest content paints fast), low INP (interactions feel instant — this replaced FID), and near-zero CLS (no layout shift). Serve next-gen images with explicit width/height, lazy-load below the fold, and check PageSpeed Insights on mobile, which is what Google measures.

  7. 7

    Verify, submit, and monitor in Google Search Console

    Add and verify your domain, submit the sitemap, and use URL Inspection to see exactly how Googlebot renders a page. Watch the Coverage/Pages and Core Web Vitals reports weekly for the first month to catch indexing errors early — this is your ground truth, not a third-party tool's estimate.

Starter snippet

tsx
// app/blog/[slug]/page.tsx — Next.js App Router
// (metadataBase is set once in app/layout.tsx)
import type { Metadata } from "next";
import { getPost } from "@/lib/posts";

type Props = { params: Promise<{ slug: string }> };

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug } = await params;
  const post = await getPost(slug);
  return {
    title: post.title,
    description: post.excerpt,
    alternates: { canonical: `/blog/${slug}` },
    openGraph: { type: "article", title: post.title, description: post.excerpt },
  };
}

export default async function Page({ params }: Props) {
  const { slug } = await params;
  const post = await getPost(slug);
  const jsonLd = {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    headline: post.title,
    datePublished: post.publishedAt,
  };
  return (
    <article>
      <script type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
      <h1>{post.title}</h1>
      {/* server-rendered body — visible to crawlers in the initial HTML */}
    </article>
  );
}

✕ Watch out for

  • Rendering SEO-critical content client-side (behind useEffect or a loading spinner) so crawlers get an empty shell — always confirm with curl or 'view source' that the text is in the raw HTML.
  • Leaving a staging noindex meta tag or 'Disallow: /' in robots.txt live in production, which quietly deindexes the entire site.
  • Shipping duplicate or auto-generated titles and meta descriptions across pages, or missing canonical tags so www/non-www and query-string URLs compete with each other.
  • Chasing dead tactics (keyword stuffing, meta keywords, link buying) instead of the things that actually move rankings now: fast Core Web Vitals and genuinely useful content.
  • Ignoring INP and mobile performance — Google measures the mobile experience, and janky interactions hurt both ranking and conversions.

✓ Pro tips

  • When you hand this to an AI coding agent, tell it your exact framework and version and say 'use the native metadata conventions — generateMetadata, sitemap.ts, robots.ts, opengraph-image.tsx — do not hand-roll <head> tags.' Then verify its work yourself: run `curl -s https://yoursite.com/some-page | grep -i '<title>'` and confirm the title, description, and JSON-LD are actually in the server HTML.
  • Ask the agent to add JSON-LD for every page type, then paste each URL into Google's Rich Results Test — agents frequently produce schema with the wrong @type or missing required fields.
  • Set metadataBase once in your root layout and use relative canonical URLs everywhere else — it prevents an entire class of duplicate-URL bugs.
  • Don't wait for 'perfect' content: verify the domain in Google Search Console and submit your sitemap on day one, because indexing takes days to weeks and the clock only starts once Google knows you exist.