How to Improve SEO in Next.js 15 with Smarter Script and Metadata Management

Learn how to boost SEO in your Next.js 15 app using smarter script loading, the new metadata API, and structured data. A practical, developer-focused guide with real examples.

Picture of Ferhat Kefsiz

Ferhat Kefsiz

on

July 15, 2025

28 views
How to Improve SEO in Next.js 15 with Smarter Script and Metadata Management

Search engines still drive tons of traffic, and even a React-based site needs to be discoverable and fast. With Next.js 15’s App Router, we have new tools to make SEO easier. Over the past few months I’ve dug into smarter ways to load scripts and set metadata, and it turns out these make a big difference not just in page speed but in how Google sees our content.

I’ll cover script strategies, the new metadata API, structured data, sitemaps, and some quick wins to bump up your SEO.

Why SEO Still Matters for React Apps

Even with a modern SPA framework, SEO is crucial. If you build an app that only hydrates after load, crawlers might see an “empty” page and miss your content.

Next.js solves this by server-rendering or statically generating pages, but you still need to optimize content, speed, and structure. For example, Google explicitly considers user-centric performance (Core Web Vitals) a ranking factor, so heavy scripts or layout shifts can hurt you. To keep crawlers and users happy, follow React SEO best practices:

  • Content delivery:: Use SSR/SSG (via Next.js) so bots see real content. As one guide notes, SSR “sends fully rendered HTML” so search bots don’t have to wait on JavaScript to index the page.
  • Performance: Make pages fast. High TTI/FCP or janky layouts can drop rankings. In fact, performance and Core Web Vitals are “a primary SERP ranking factor
  • Clean URLs:: Ensure every page has its own URL (no single “index.html” for everything). Unique URLs with good metadata help Google index more of your app.

Keeping these in mind as a Next.js dev means pairing great React features with solid SEO fundamentals. On to the first trick: smarter script management.

Smarter Script Management with next/script

Loading third-party scripts (analytics, chat widgets, ads, etc.) is a common pitfall. If a big script tag blocks the main thread, it can delay rendering or cause layout jumps, both of which damage SEO. Next.js offers the <Script> component to fix this. Instead of raw script tags, use Script with a strategy prop to control when each script loads. The main options are:

  • beforeInteractive: Loads prior to hydration. This strategy inlines the script in the HTML head with defer so it runs early but without blocking parsing. Use it for critical third-party code that your page needs immediately (e.g. essential analytics or security scripts).
  • afterInteractive (default): Loads after the page is interactive, similar to defer. Put non-critical scripts here, like Google Analytics or ad pixels, so they don’t delay content.
  • lazyOnload: Loads when the browser is idle (via requestIdleCallback). Perfect for very low-priority features (chat widgets, social embeds) that can wait until after the initial content is rendered.

This filmstrip illustrates the effect of using <Script> optimally. With the right strategy, the main content paints faster and shifts less, improving LCP and CLS. You can see content appearing noticeably sooner on the right side (using <Script> correctly) than on the left (heavy scripts blocking the load). In practice, you might do something like:

1
import Script from "next/script"
2
3
export default function MyPage() {
4
return (
5
<>
6
<head>
7
{/* Critical analytics before interaction */}
8
<Script
9
src="https://example.com/critical.js"
10
strategy="beforeInteractive"
11
/>
12
</head>
13
<body>
14
<h1>My Page Content</h1>
15
{/* Standard analytics after hydration */}
16
<Script
17
src="https://www.googletagmanager.com/gtag/js?id=G-XYZ"
18
strategy="afterInteractive"
19
/>
20
{/* Lazy-load a chat widget */}
21
<Script src="https://example.com/chat.js" strategy="lazyOnload" />
22
</body>
23
</>
24
)
25
}

This approach ensures that scripts don’t block or shift the main content. In fact, Chrome’s Next.js team showed that using <Script> with afterInteractive/lazyOnload can shave seconds off your LCP compared to raw scripts. You also get callbacks (onLoad, onReady, onError) to manage script events.

Metadata and Structured Data in Next.js 15

Next.js 15’s App Router makes managing page metadata super clean. You no longer manually jam tags into _app or custom head – instead export a metadata object or function from each layout/page. For example, static pages can simply do:

1
export const metadata = {
2
title: 'My Home Page',
3
description: 'Welcome to my Next.js site!'
4
};
5
export default function HomePage() { ... }

This ensures the <title> and <meta> tags are correct for that page. For dynamic content (like blog posts), use generateMetadata() to fetch data and return metadata per page. For example:

1
export async function generateMetadata({ params }) {
2
const post = await getPost(params.slug)
3
return {
4
title: `Post: ${post.title}`,
5
description: post.summary,
6
}
7
}

This way each post gets unique titles/descriptions. Next.js 15 even lets you use template and absolute to DRY up titles (e.g. appending your site name by default) and override when needed.

The metadata object also supports Open Graph and Twitter tags built-in. For instance, you can include an openGraph field with an image, site name, etc. all in one place. This makes social previews and SEO consistent without extra code.

Beyond classic meta tags, structured data (JSON-LD) is a big win. Search engines love schema.org data for rich results. In Next.js you usually add JSON-LD by injecting a <script type="application/ld+json"> in your page. The official guide shows this pattern: create a JS object with schema and do:

1
<script
2
type="application/ld+json"
3
dangerouslySetInnerHTML={{
4
__html: JSON.stringify({
5
"@context": "https://schema.org",
6
"@type": "Product",
7
name: product.name,
8
description: product.description,
9
image: product.image,
10
}).replace(/</g, "\\u003c"),
11
}}
12
/>

This can live inside your layout or page and won’t render on screen, but Google will see it. Using JSON-LD like this (with escaped) ensures your structured data is safe and indexable. In practice, add schema for articles, products, events, etc., wherever it fits – it tells search engines exactly what’s on the page.

Bonus: Sitemaps, Canonical Tags, and Quick Wins

  • Sitemaps: A sitemap is simply a file listing all important URLs on your site, helping crawlers understand its structure. In Next.js 15 you can auto-generate one by creating a sitemap.ts under app/ that returns your URLs. For example, using the new API you might write a function that fetches all product IDs and returns [ { url: 'https://example.com/product/123', lastModified: '2025-07-14' }, ... ] format. Next will serve this as /sitemap.xml, keeping search engines up to date automatically.
  • Canonical Tags: Always indicate the “single source” URL for content that may appear at multiple routes. Add <link rel="canonical" href="https://yoursite.com/your-page" /> in the head for any page. Canonical tags tell Google which URL is the authoritative one, preventing duplicate content issues (e.g. pages with UTM params or trailing slashes). In the App Router you can do this via the alternates or metadataBase field in your metadata, or by outputting a canonical link in a component.
  • Alt text for images: Every image should have a meaningful alt attribute. Alt text improves accessibility and SEO, since it helps search engines know what an image is about. When using Next’s <Image> component, always set alt="..." on each image.
  • Semantic HTML: Use proper tags for your content. Wrap blog posts in <article>, put navigation in <nav>, use <header>/<footer> etc. Semantic markup gives search crawlers clues about structure and importance. For example, use <h1> for the main title and use <section> or <article> for logical page divisions. This clean structure helps bots index your site more intelligently.