DEV Community

javediqbal8381
javediqbal8381

Posted on

SEO for Next.js apps — practical tips that actually work

Most

SEO posts rehash the same basics. This one is for developers who already know the basics and want practical Next.js-specific tactics that you can apply today to get better indexing, prettier link previews, and faster real-user experience.

1) Use the Metadata API (app router) — per page, correctly

If you use the app router, export metadata or generateMetadata from the page/layout. It’s the cleanest way to keep titles, descriptions, canonical links, open graph and favicons consistent per route. When metadata is rendered server side, crawlers and social previews see the right content immediately.

Example (Static):

// app/blog/[slug]/page.js

export const metadata = {
  title: 'How to optimize a blog post',
  description: 'Practical SEO tips for Next.js blog posts',
  alternates: { canonical: 'https://yourdomain.com/blog/my-post' },
  openGraph: {
    title: 'How to optimize a blog post',
    description: 'Practical SEO tips for Next.js',
    images: [{ url: '/og-images/blog-my-post.png', width: 1200, height: 630 }],
  },
}
Enter fullscreen mode Exit fullscreen mode

Example (Dynamic):
// app/blog/[slug]/page.js

export async function generateMetadata({ params }) {
  const post = await getPost(params.slug)
  return {
    title: post.title,
    description: post.excerpt,
    alternates: { canonical: `https://yourdomain.com/blog/${params.slug}` },
  }
}
Enter fullscreen mode Exit fullscreen mode

2) Own your sitemap and robots with the app router

Sitemaps still matter. Next.js has first-class helpers for sitemaps in the app router so you can generate sitemaps at build time or split them for very large sites. Keep your sitemap up to date and submit it to Search Console after major updates.

Tiny example (app router):

// app/sitemap.ts

import { MetadataRoute } from 'next'
import { getAllSlugs } from './lib/cms'

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const slugs = await getAllSlugs()
  return slugs.map(s => ({
    url: `https://yourdomain.com/blog/${s}`,
    lastModified: new Date().toISOString(),
  }))
}
Enter fullscreen mode Exit fullscreen mode

Then submit https://yourdomain.com/sitemap.xml to Google Search Console, or list it in robots.txt.

3) Server-render critical content, avoid client-only for SEO text

Google processes JavaScript in stages: crawl, render, index. If your primary content is rendered only on the client, indexing may be delayed or incomplete. Render key content server side (SSG/SSR/ISR) so crawlers immediately see it. Dynamic rendering exists as a fallback but is not recommended long term.

Quick guideline:

  1. Marketing pages and blog posts: SSG or ISR.
  2. Frequently changing lists (search results): SSR or carefully designed hybrid.
  3. Personalised dashboard UI: client-side is fine (not for SEO).

4) Make images SEO-friendly using next/image

Images affect page speed and visual stability. Use next/image for automatic resizing, modern formats and to avoid layout shift. Always include good alt text and a sensible sizes / priority policy for hero images.

Example:

import Image from 'next/image'

export default function Hero({ post }) {
  return (
    <Image
      src={post.hero}
      alt={post.title}
      width={1200}
      height={630}
      priority
      sizes="(max-width: 768px) 100vw, 1200px"
    />
  )
}
Enter fullscreen mode Exit fullscreen mode

5) Use JSON-LD structured data server side for rich results

JSON-LD still makes it easier for Google to understand your content and can unlock rich snippets like article cards, FAQs, product info and more. Inject JSON-LD from the server so crawlers see it immediately, then validate with Google’s Rich Results Test. Google recommends JSON-LD.

Example (server component):

const jsonLd = {
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": post.title,
  "datePublished": post.publishedAt,
  "author": { "@type": "Person", "name": post.author },
  "image": post.heroFullUrl,
  "publisher": {
    "@type": "Organization",
    "name": "Your Site",
    "logo": { "@type": "ImageObject", "url": "https://yourdomain.com/logo.png" }
  }
}

export default function HeadTags() {
  return (
    <>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />
    </>
  )
}

Enter fullscreen mode Exit fullscreen mode

Out-of-the-box ideas worth trying

  • Serve simplified content to crawlers with server components — render a minimal SEO-first layer server-side and hydrate interactive bits later.
  • Split large sitemaps dynamically — for very large sites, break sitemaps by date or category so Search Console processes them faster. Next.js supports splitting sitemaps.
  • Next.js
  • Generate OG images at build time or on demand using Next.js image APIs so every post has a unique share image without manual work. (Metadata API can help here.)
  • Next.js
  • Use revalidate tags to invalidate groups of pages instead of single paths when your CMS updates a taxonomy. This keeps content consistent without triggering thousands of rebuilds.

Top comments (0)