
How to Make Your Next.js App Highly SEO Optimized: Metadata, LD+JSON, Google Search Console, Bing & More
So you built a beautiful Next.js app. The UI is clean, the code is smooth, and you're genuinely proud of it. There's just one problem — Google has no idea it exists. 😅
SEO is one of those things developers push to the end of the to-do list. And that's exactly where a lot of great projects quietly die. No traffic. No visibility. No love.
The good news? Next.js is one of the most SEO-friendly frameworks out there. But "SEO-friendly" only means something if you set things up right. You need proper metadata, structured data (LD+JSON), a sitemap, a robots file — and then you need to actually tell Google and Bing your site exists.
That last part is something most tutorials completely skip. Your site doesn't magically appear in search results the moment you deploy. You have to register it, verify ownership, and submit your sitemap. Let's cover everything from start to finish. 🚀
What Does "SEO Optimized" Actually Mean?
SEO stands for Search Engine Optimization. At its core, it means making your website easy for search engines like Google to read, understand, and rank.
Think of Google as a massive library and Googlebot as the librarian. The librarian crawls every webpage it finds, reads the content, and decides where to file it. SEO is how you help that librarian understand exactly what your page is about and why it deserves a good spot.
For a Next.js app, this covers four areas:
- Technical SEO — server rendering, page speed, clean HTML structure
- On-page SEO — titles, descriptions, headings, canonical URLs
- Structured Data — LD+JSON schemas that give Google extra context
- Discovery — telling Google and Bing your site exists via Search Console tools
Next.js handles the technical foundation well. Your job is to build on top of it.
Why This Matters for Your Next.js App
You can have the best product in the world, but if it doesn't appear in search results, it effectively doesn't exist for most users.
Here's the practical reality:
- Organic traffic is free. Unlike paid ads, a well-optimized page can bring consistent visitors for months or years at zero cost.
- Better rankings mean more trust. Users instinctively trust pages that rank higher.
- SEO and good UX go hand in hand. Fast pages, clean structure, and clear content help both users and crawlers.
- Career value. Developers who understand SEO are significantly more valuable to any team building a public-facing product.
Whether you're building a portfolio, blog, SaaS, or tool — SEO matters from day one.
Part 1: Setting Up Metadata in Next.js (App Router)
The App Router (Next.js 13+) introduced a clean Metadata API that replaces manually managing <head> tags. This is the foundation of all on-page SEO.
Static Metadata
Export a metadata object from any page.tsx or layout.tsx:
// app/layout.tsx
import type { Metadata } from "next";
export const metadata: Metadata = {
metadataBase: new URL("https://hamidrazadev.com"),
title: "Hamid Raza — Frontend Developer & Blogger",
description: "Tutorials, tips, and tools for modern web development.",
keywords: ["Next.js", "React", "Frontend Development", "Web Dev Blog"],
authors: [{ name: "Hamid Raza", url: "https://hamidrazadev.com" }],
creator: "Hamid Raza",
openGraph: {
title: "Hamid Raza — Frontend Developer & Blogger",
description: "Tutorials, tips, and tools for modern web development.",
url: "https://hamidrazadev.com",
siteName: "Hamid Raza Dev",
images: [
{
url: "/og-image.png",
width: 1200,
height: 630,
alt: "Hamid Raza Developer Blog",
},
],
locale: "en_US",
type: "website",
},
twitter: {
card: "summary_large_image",
title: "Hamid Raza — Frontend Developer & Blogger",
description: "Tutorials, tips, and tools for modern web development.",
images: ["/og-image.png"],
creator: "@hamidrazadev",
},
robots: {
index: true,
follow: true,
},
alternates: {
canonical: "https://hamidrazadev.com",
},
};
💡 Important: Always set
metadataBase. Without it, Next.js can't resolve relative image URLs for Open Graph, and your social previews will break silently.
Dynamic Metadata for Blog Posts
For pages like /blog/[slug], use generateMetadata():
// app/blog/[slug]/page.tsx
import type { Metadata } from "next";
type Props = { params: { slug: string } };
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const post = await getPostBySlug(params.slug);
return {
title: `${post.title} — Hamid Raza Dev Blog`,
description: post.excerpt,
alternates: {
canonical: `https://hamidrazadev.com/blog/${params.slug}`,
},
openGraph: {
title: post.title,
description: post.excerpt,
url: `https://hamidrazadev.com/blog/${params.slug}`,
type: "article",
publishedTime: post.publishedAt,
authors: ["Hamid Raza"],
images: [{ url: post.thumbnail, width: 1200, height: 630, alt: post.title }],
},
};
}
Every post now gets unique, accurate metadata — automatically. ✅
Adding the Google Verification Tag via Metadata
When you verify your site with Google Search Console (covered later), one method is adding a verification meta tag. In Next.js App Router, you can do this directly in your root layout.tsx:
// app/layout.tsx
export const metadata: Metadata = {
// ...your other metadata
verification: {
google: "YOUR_VERIFICATION_CODE_FROM_GOOGLE",
},
};
This generates the <meta name="google-site-verification" content="..."> tag in your <head> automatically. No manual HTML editing required.
Note: There's a known quirk in some Next.js versions where the
verificationdoesn't render as expected. If you run into that, use theotherfield instead:
verification: {
other: {
"google-site-verification": "YOUR_VERIFICATION_CODE",
},
},
Both produce the exact same meta tag in your HTML.
Part 2: Adding LD+JSON Structured Data
Structured data (LD+JSON) is how you speak Google's language. It gives search engines extra context about your content in a machine-readable format — and it can unlock rich results in search: article previews, FAQ dropdowns, breadcrumbs, and more.
Reusable JsonLd Component
// components/JsonLd.tsx
type JsonLdProps = {
data: Record<string, unknown>;
};
export default function JsonLd({ data }: JsonLdProps) {
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }}
/>
);
}
Blog Post Schema (BlogPosting)
// app/blog/[slug]/page.tsx
import JsonLd from "@/components/JsonLd";
export default function BlogPost({ post }) {
const schema = {
"@context": "https://schema.org",
"@type": "BlogPosting",
headline: post.title,
description: post.excerpt,
image: post.thumbnail,
datePublished: post.publishedAt,
dateModified: post.updatedAt,
author: {
"@type": "Person",
name: "Hamid Raza",
url: "https://hamidrazadev.com",
},
publisher: {
"@type": "Organization",
name: "Hamid Raza Dev",
logo: {
"@type": "ImageObject",
url: "https://hamidrazadev.com/logo.png",
},
},
mainEntityOfPage: {
"@type": "WebPage",
"@id": `https://hamidrazadev.com/blog/${post.slug}`,
},
};
return (
<>
<JsonLd data={schema} />
{/* blog content */}
</>
);
}
Person Schema (for Portfolio Sites)
{
"@context": "https://schema.org",
"@type": "Person",
"name": "Hamid Raza",
"url": "https://hamidrazadev.com",
"jobTitle": "Frontend Developer",
"sameAs": [
"https://twitter.com/hamidrazadev",
"https://github.com/hamidrazadev",
"https://linkedin.com/in/hamidrazadev"
]
}
FAQ Schema (Great for Organic Traffic)
FAQ schemas can appear as expandable dropdowns directly in Google's search results, which dramatically improves click-through rate.
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "What is Next.js?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Next.js is a React framework for production-grade web applications with built-in SSR and SSG support."
}
}
]
}
🔧 Always test your structured data using Google's Rich Results Test before publishing. A small syntax error can make Google ignore your entire schema silently.
Part 3: Sitemap and robots.txt
Auto-Generated Sitemap
// app/sitemap.ts
import { MetadataRoute } from "next";
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const posts = await getAllPosts();
return [
{
url: "https://hamidrazadev.com",
lastModified: new Date(),
changeFrequency: "monthly",
priority: 1,
},
...posts.map((post) => ({
url: `https://hamidrazadev.com/blog/${post.slug}`,
lastModified: new Date(post.updatedAt),
changeFrequency: "weekly" as const,
priority: 0.8,
})),
];
}
This auto-generates your sitemap at /sitemap.xml. Every new post gets included automatically.
robots.txt
// app/robots.ts
import { MetadataRoute } from "next";
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: "*",
allow: "/",
disallow: ["/api/", "/admin/", "/dashboard/"],
},
sitemap: "https://hamidrazadev.com/sitemap.xml",
};
}
Part 4: Adding Your Site to Google Search Console
This is the step most tutorials skip — and it's arguably the most important. Just having a sitemap isn't enough. You need to register your site with Google so it knows to crawl it, and so you can monitor performance, fix issues, and track which pages are indexed.
Step 1: Go to Google Search Console
Head to search.google.com/search-console and sign in with your Google account.
Step 2: Add a Property
Click "Add Property". You'll see two options:
| Property Type | What It Covers | Verification Method |
|---|---|---|
| Domain | All subdomains, HTTP + HTTPS (e.g., hamidrazadev.com) | DNS TXT record only |
| URL Prefix | One specific URL (e.g., https://hamidrazadev.com) | Multiple methods |
Recommendation: Use the Domain property type. It covers everything under your domain in one place — www, non-www, HTTP, HTTPS, and all subdomains. This gives you a complete picture in one dashboard.
Step 3: Verify Ownership
Google needs to confirm you actually own the site. There are five verification methods:
Method 1: DNS TXT Record (Recommended for Domain properties)
Google gives you a TXT record that looks like this: google-site-verification=XXXXXXXXXXXXXXX
Log into your domain provider (Namecheap, GoDaddy, Hostinger, Cloudflare, etc.) and add this as a TXT DNS record. Leave the "Name/Host" field blank or set it to @. The propagation usually takes a few minutes, but Google says it can sometimes take up to 24–48 hours.
This is the most reliable method because it doesn't depend on any code in your app. It stays valid even if you rebuild or redeploy your site.
Method 2: HTML Meta Tag (URL Prefix only)
Google gives you a meta tag like:
<meta name="google-site-verification" content="YOUR_CODE" />
In Next.js, add this via the verification field in your root layout.tsx as shown in Part 1. Google checks for this tag on your homepage and verifies your ownership quickly.
Method 3: HTML File Upload
Download a small HTML file from Google and upload it to your Next.js /public folder. The file becomes accessible at https://yourdomain.com/google-site-verification.html. Google fetches it and verifies you.
/public/googleXXXXXXXXXXX.html
Method 4: Google Analytics
If you already have Google Analytics 4 installed on your site (using the same Google account), you can verify via that. Google just checks that the GA tracking code is present on your homepage.
Method 5: Google Tag Manager
Same concept — if GTM is active on your site and your Google account has access, GTM can be used for verification.
💡 You can keep multiple verification methods active at the same time. Many developers use both DNS and the HTML meta tag for redundancy — if one breaks during a deployment, the other keeps verification active.
Step 4: Submit Your Sitemap
Once verified, go to Indexing → Sitemaps in the left menu. Enter your sitemap URL:
https://hamidrazadev.com/sitemap.xml
Click Submit. Google will start reading your sitemap and scheduling pages for crawling. You can check back in a day or two to see how many pages were discovered.
Step 5: Use the URL Inspection Tool
This is one of the most useful tools in Google Search Console. You can paste any URL from your site and see:
- Whether it's indexed or not
- When Google last crawled it
- What the rendered HTML looks like (after JavaScript runs)
- Whether your structured data was detected correctly
- Any crawl or indexing errors
If a page isn't indexed yet and you want to speed things up, click "Request Indexing" inside the URL Inspection tool. Google adds that URL to its priority crawl queue. This doesn't guarantee immediate indexing, but it usually cuts the wait from days down to hours for healthy pages.
Keep in mind: There's a daily quota on indexing requests. Use it strategically — for new important pages, after major content updates, or after fixing a technical issue.
Part 5: Adding Your Site to Bing Webmaster Tools
Bing is the world's second-largest search engine and powers searches across DuckDuckGo, Yahoo, and even some ChatGPT web queries. Ignoring Bing means ignoring a portion of your potential audience.
The good news: if you've already set up Google Search Console, getting into Bing Webmaster Tools is extremely fast.
Step 1: Go to Bing Webmaster Tools
Go to bing.com/webmasters and sign in. You can use a Microsoft account, a Google account, or a Facebook account.
Step 2: Choose How to Add Your Site
Bing gives you two paths right away:
🚀 Fast Path: Import from Google Search Console
This is by far the easiest and fastest method. Since your site is already verified in GSC, Bing can import it directly — no separate verification needed.
- Click "Import from Google Search Console"
- Click "Continue" and sign in with the same Google account that has access to your GSC property
- Grant Bing permission to access your GSC data
- Select the sites you want to import and click "Import"
That's it. Your site is added and automatically verified. Bing also imports your existing sitemaps. It may take up to 48 hours for traffic data to start appearing in the dashboard.
Note: Bing periodically validates your ownership by syncing with your GSC account. If you ever revoke GSC access, you'll need to re-verify using one of the manual methods below.
Manual Path: Add Site Manually
If you'd prefer not to connect your GSC account, you can add the site manually:
- Enter your site URL under "Add your site manually" and click "Add"
- Choose a verification method
Bing's manual verification methods are:
| Method | How It Works |
|---|---|
| Domain Connect | Bing automatically connects to supported DNS providers (GoDaddy, etc.) and verifies instantly |
| XML File Upload | Download an XML file from Bing and upload it to your site's root directory |
| HTML Meta Tag | Add a Bing-specific meta tag to your homepage's <head> |
| DNS TXT / CNAME Record | Add a TXT or CNAME record to your domain's DNS settings |
The meta tag method is the most practical for Next.js developers. Bing gives you a tag like this:
<meta name="msvalidate.01" content="YOUR_BING_VERIFICATION_CODE" />
In Next.js, add it to your root layout.tsx:
export const metadata: Metadata = {
// ...
verification: {
other: {
"msvalidate.01": "YOUR_BING_VERIFICATION_CODE",
},
},
};
Deploy, then return to Bing Webmaster Tools and click "Verify".
Step 3: Submit Your Sitemap to Bing
Even if you imported from GSC, it's worth manually confirming your sitemap in Bing.
- In the left sidebar, go to Sitemaps
- Click "Submit sitemap"
- Enter your sitemap URL:
https://yourdomain.com/sitemap.xml - Click Submit
Bing will start crawling and indexing your pages from the sitemap.
Step 4: Use the URL Inspection Tool
Just like GSC, Bing Webmaster Tools has a URL Inspection tool. You can check if a specific URL is indexed in Bing, see when it was last crawled, and spot any crawl errors. It also shows whether your page is blocked by robots.txt or has other issues preventing indexing.
Part 6: IndexNow — Tell Bing About New Pages Instantly
This is a bonus level that most developers don't know about. 👀
IndexNow is an open protocol that lets you instantly notify search engines when you publish or update content. Instead of waiting for Bingbot to eventually crawl your site on its own schedule, you proactively send a ping saying "hey, this page just changed."
Currently, IndexNow is supported by Bing, Yandex, DuckDuckGo (via Bing), Naver, Seznam, and Yep. When you submit to one, all participating engines get notified automatically. Google doesn't support IndexNow yet.
Setting It Up in Next.js
Step 1: Generate an API key
Go to Bing's IndexNow page and generate a key. It's a random hex string.
Step 2: Host the key file
Create a .txt file in your /public folder named after your key:
/public/f34f184d10c049ef99aa7637cdc4ef04.txt
The file contents should just be the key itself:
f34f184d10c049ef99aa7637cdc4ef04
This makes the key accessible at https://yourdomain.com/f34f184d10c049ef99aa7637cdc4ef04.txt, which proves you own the domain.
Step 3: Create a submit utility
// lib/indexnow.ts
const INDEXNOW_KEY = process.env.INDEXNOW_API_KEY!;
const HOST = "https://hamidrazadev.com";
export async function submitToIndexNow(urls: string[]) {
const response = await fetch("https://api.indexnow.org/indexnow", {
method: "POST",
headers: { "Content-Type": "application/json; charset=utf-8" },
body: JSON.stringify({
host: HOST,
key: INDEXNOW_KEY,
keyLocation: `${HOST}/${INDEXNOW_KEY}.txt`,
urlList: urls,
}),
});
if (!response.ok) {
console.error("IndexNow submission failed:", response.status);
}
}
Step 4: Call it when you publish content
// Example: after publishing a new blog post
await submitToIndexNow([
"https://hamidrazadev.com/blog/my-new-post",
]);
You can submit up to 10,000 URLs per day per domain. For most blogs and apps, just calling this whenever you publish or update a page is more than enough.
Store your key in
.env.localasINDEXNOW_API_KEY. Never hardcode it directly in your source files.
Part 7: Core Web Vitals & Performance Tips
Google uses Core Web Vitals as a direct ranking factor. Here's what matters:
LCP (Largest Contentful Paint) — how fast the main content loads.
Always use next/image and add priority to your hero image:
import Image from "next/image";
<Image src="/hero.png" alt="Hero" width={1200} height={630} priority />
CLS (Cumulative Layout Shift) — how much the layout jumps around.
Always define width and height for images. Reserve space for dynamic content like ads or embeds.
INP (Interaction to Next Paint) — how quickly the page responds to user input.
Minimize heavy client-side JavaScript. Use dynamic imports for non-critical components:
import dynamic from "next/dynamic";
const HeavyComponent = dynamic(() => import("@/components/HeavyComponent"), {
ssr: false,
loading: () => <p>Loading...</p>,
});
Font optimization:
import { Inter } from "next/font/google";
const inter = Inter({ subsets: ["latin"], display: "swap" });
next/font self-hosts Google Fonts and eliminates render-blocking font requests.
Best Tips & Do's and Don'ts
Do: Use semantic HTML. One <h1> per page. Use <article>, <main>, <section>, and <nav> properly. Google reads your HTML structure.
Do: Set canonical URLs on every page. This prevents duplicate content penalties when your page is accessible via multiple URLs.
Do: Write unique titles and descriptions for every page. Generic or duplicate metadata is one of the most common SEO mistakes and directly hurts rankings.
Do: Link internally. Every blog post should link to 2–3 related pages on your site. It helps Google understand your content structure and passes authority between pages.
Don't: Forget metadataBase in your root layout. Without it, all relative Open Graph image URLs will break.
Don't: Remove your DNS TXT record after verification. Google requires it to stay in place permanently. Removing it will de-verify your property.
Don't: Lazy-load images that are visible on first load. Only lazy-load content below the fold. Use priority for anything above the fold.
Don't: Block important pages in robots.txt by accident. Always double-check your disallow rules before deploying.
Common Mistakes to Avoid
Not submitting the sitemap after adding GSC.
Verifying your site doesn't automatically tell Google which pages exist. You need to submit /sitemap.xml separately in the Sitemaps section.
Using duplicate metadata across all pages. Every page needs a unique title and description. Google can penalize or deprioritize pages with the same metadata as other pages on your site.
Forgetting metadataBase.
All relative Open Graph image paths break silently without it. This is the number one hidden bug in Next.js SEO setups.
Not validating structured data. A small typo in your LD+JSON schema will cause Google to silently ignore it. Always run it through the Rich Results Test before publishing.
Skipping Bing entirely. Bing powers DuckDuckGo, Yahoo, and Microsoft Edge search. It also feeds some AI chatbot search results. It only takes five minutes to set up via the GSC import — don't skip it.
Requesting indexing too aggressively. There's a daily quota on "Request Indexing" in Google Search Console. Use it for important pages, not every small update. For bulk updates, submit your sitemap instead.
Deploying without testing verification methods.
After adding your Google/Bing verification meta tags to Next.js, deploy and check view-source:yourdomain.com to confirm the tags are actually rendering in the HTML before you click "Verify" in either console.
Conclusion
Getting your Next.js app properly set up for SEO is a multi-step process — but each step is straightforward when you know what you're doing.
Here's the full checklist:
- ✅ Set up Metadata API with titles, descriptions, Open Graph, and Twitter cards
- ✅ Add LD+JSON structured data for blog posts, FAQs, and site info
- ✅ Generate a dynamic
sitemap.xmlandrobots.txt - ✅ Verify your site in Google Search Console (DNS TXT is most reliable)
- ✅ Submit your sitemap to Google
- ✅ Add to Bing Webmaster Tools — import from GSC for instant setup
- ✅ Submit sitemap to Bing too
- ✅ Use URL Inspection to check indexing status and request indexing for key pages
- ✅ Set up IndexNow to notify Bing about new content instantly
- ✅ Optimize Core Web Vitals using
next/image,next/font, and dynamic imports
None of this is complicated on its own. The trick is doing all of it together. When everything is in place, Google and Bing can find your pages, understand them, and rank them — and that's when the real traffic starts flowing. 🚀
Got questions or something that helped you? Drop a comment below. And if you want more practical guides like this one, there's a lot more waiting for you at hamidrazadev.com — see you there. 😊
Muhammad Hamid Raza
Content Author
Originally published on Dev.to • Content syndicated with permission
