🗺️ SEO Files in Next.js: Your Website’s Tour Guides
The Story of Lost Websites
Imagine the internet is a massive library with billions of books. Google is the helpful librarian who tells visitors where to find what they’re looking for.
But here’s the problem: How does the librarian know what’s in YOUR book?
That’s where SEO files come in! They’re like special notes you leave for the librarian, saying:
- “Here’s a pretty picture of my book cover!” (ImageResponse API)
- “Don’t look at my private diary pages!” (robots.ts)
- “Here’s a complete map of my book!” (sitemap.ts)
- “My book is SO big, here are multiple maps!” (generateSitemaps)
Let’s meet each of these helpers!
🖼️ ImageResponse API: Creating Beautiful Preview Pictures
What Is It?
When you share a link on Twitter, LinkedIn, or WhatsApp, you see a preview image. That’s called an Open Graph image or OG image.
Think of it like this: Before someone visits your house, they see a photo of it. A beautiful photo makes them want to come inside!
The Magic Behind It
The ImageResponse API lets you create images using code. No Photoshop needed!
// app/about/opengraph-image.tsx
import { ImageResponse } from 'next/og'
export const runtime = 'edge'
export const alt = 'About My Company'
export const size = { width: 1200, height: 630 }
export const contentType = 'image/png'
export default function Image() {
return new ImageResponse(
(
<div style={{
fontSize: 60,
background: 'linear-gradient(to right, #4F46E5, #7C3AED)',
color: 'white',
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}>
Welcome to My Website! 🚀
</div>
),
{ ...size }
)
}
How It Works
graph TD A["Someone shares your link"] --> B["Social media asks for image"] B --> C["Next.js runs your code"] C --> D["ImageResponse creates image"] D --> E["Beautiful preview appears!"]
Key Points
| Property | What It Does |
|---|---|
runtime: 'edge' |
Makes it super fast |
alt |
Describes image for blind users |
size |
Width and height in pixels |
contentType |
Usually image/png |
Special File Names
opengraph-image.tsx→ For Facebook, LinkedIntwitter-image.tsx→ For Twitter/Xicon.tsx→ For browser tab icons
Pro tip: You can use the same code for both! Just export from two files.
🤖 robots.ts: Your Website’s Security Guard
What Is It?
Imagine your website is a house. Some rooms are for guests (public pages). Some rooms are private (admin panel, checkout, etc.).
The robots.ts file is like a security guard who tells search engines:
- “Yes, you can look at this room!”
- “No, stay away from that room!”
The Simple Version
// app/robots.ts
import { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: '*',
allow: '/',
disallow: '/private/',
},
sitemap: 'https://mysite.com/sitemap.xml',
}
}
Understanding Each Part
| Part | Meaning |
|---|---|
userAgent: '*' |
All search engines |
allow: '/' |
They CAN visit everything |
disallow: '/private/' |
But NOT the /private folder |
sitemap |
“Here’s my map, use it!” |
Multiple Rules Example
export default function robots(): MetadataRoute.Robots {
return {
rules: [
{
userAgent: 'Googlebot',
allow: '/',
disallow: '/admin/',
},
{
userAgent: 'Bingbot',
allow: '/',
disallow: ['/admin/', '/api/'],
},
],
sitemap: 'https://mysite.com/sitemap.xml',
}
}
What Gets Generated
Your code becomes a text file that looks like:
User-Agent: Googlebot
Allow: /
Disallow: /admin/
User-Agent: Bingbot
Allow: /
Disallow: /admin/
Disallow: /api/
Sitemap: https://mysite.com/sitemap.xml
🗺️ sitemap.ts: Your Website’s Complete Map
What Is It?
A sitemap is like a table of contents for your website. It tells Google:
- “Here are ALL my pages!”
- “This page was updated yesterday!”
- “This page is REALLY important!”
Basic Sitemap
// app/sitemap.ts
import { MetadataRoute } from 'next'
export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: 'https://mysite.com',
lastModified: new Date(),
changeFrequency: 'yearly',
priority: 1,
},
{
url: 'https://mysite.com/about',
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.8,
},
{
url: 'https://mysite.com/blog',
lastModified: new Date(),
changeFrequency: 'weekly',
priority: 0.5,
},
]
}
Understanding the Properties
graph TD A["url"] --> B["Where is the page?"] C["lastModified"] --> D["When was it updated?"] E["changeFrequency"] --> F["How often does it change?"] G["priority"] --> H["How important is it? 0-1"]
| Property | Values | Meaning |
|---|---|---|
url |
Full URL | The page address |
lastModified |
Date | When you last edited it |
changeFrequency |
always, hourly, daily, weekly, monthly, yearly, never | How often it changes |
priority |
0.0 to 1.0 | 1 = Most important |
Dynamic Sitemap from Database
// app/sitemap.ts
import { MetadataRoute } from 'next'
export default async function sitemap(): MetadataRoute.Sitemap {
// Get all blog posts from database
const posts = await fetch('https://api.mysite.com/posts')
.then(res => res.json())
const blogUrls = posts.map((post) => ({
url: `https://mysite.com/blog/${post.slug}`,
lastModified: post.updatedAt,
}))
return [
{
url: 'https://mysite.com',
lastModified: new Date(),
},
...blogUrls,
]
}
📚 generateSitemaps: When One Map Isn’t Enough
The Problem
Imagine you have a bookstore with 100,000 books. One map would be HUGE and slow!
Google says: “Please keep sitemaps under 50,000 URLs!”
The Solution: Multiple Sitemaps
The generateSitemaps function lets you split your sitemap into smaller pieces.
// app/products/sitemap.ts
import { MetadataRoute } from 'next'
export async function generateSitemaps() {
// Count total products
const totalProducts = await getProductCount()
const productsPerSitemap = 50000
// How many sitemaps do we need?
const numberOfSitemaps = Math.ceil(
totalProducts / productsPerSitemap
)
// Return array of sitemap IDs
return Array.from(
{ length: numberOfSitemaps },
(_, i) => ({ id: i })
)
}
export default async function sitemap({
id,
}: {
id: number
}): Promise<MetadataRoute.Sitemap> {
const start = id * 50000
const end = start + 50000
const products = await getProducts(start, end)
return products.map((product) => ({
url: `https://mysite.com/products/${product.id}`,
lastModified: product.updatedAt,
}))
}
How It Works
graph TD A["You have 120,000 products"] --> B["generateSitemaps runs"] B --> C["Returns 3 sitemap IDs: 0, 1, 2"] C --> D["/products/sitemap/0.xml<br>50,000 products"] C --> E["/products/sitemap/1.xml<br>50,000 products"] C --> F["/products/sitemap/2.xml<br>20,000 products"]
The URLs Created
| Sitemap ID | URL | Contains |
|---|---|---|
| 0 | /products/sitemap/0.xml |
Products 1-50,000 |
| 1 | /products/sitemap/1.xml |
Products 50,001-100,000 |
| 2 | /products/sitemap/2.xml |
Products 100,001-120,000 |
Complete Example
// app/blog/sitemap.ts
import { MetadataRoute } from 'next'
import { getAllPosts, getPostCount } from '@/lib/posts'
const POSTS_PER_SITEMAP = 1000
export async function generateSitemaps() {
const count = await getPostCount()
const pages = Math.ceil(count / POSTS_PER_SITEMAP)
return Array.from({ length: pages }, (_, i) => ({
id: i,
}))
}
export default async function sitemap({
id,
}: {
id: number
}): Promise<MetadataRoute.Sitemap> {
const offset = id * POSTS_PER_SITEMAP
const posts = await getAllPosts(offset, POSTS_PER_SITEMAP)
return posts.map((post) => ({
url: `https://myblog.com/post/${post.slug}`,
lastModified: new Date(post.updatedAt),
changeFrequency: 'weekly',
priority: 0.7,
}))
}
🎯 Putting It All Together
Here’s how all four SEO files work as a team:
graph TD A["Your Next.js App"] --> B["robots.ts"] A --> C["sitemap.ts"] A --> D["opengraph-image.tsx"] B --> E["Tells Google what to index"] C --> F["Shows Google all your pages"] D --> G["Makes sharing look beautiful"] C --> H{Too many pages?} H -->|Yes| I["Use generateSitemaps"] H -->|No| J["Single sitemap is fine"]
Quick Reference
| File | Purpose | Example Location |
|---|---|---|
robots.ts |
Control search engines | app/robots.ts |
sitemap.ts |
List all pages | app/sitemap.ts |
opengraph-image.tsx |
Social preview images | app/opengraph-image.tsx |
generateSitemaps |
Split large sitemaps | Inside sitemap.ts |
🚀 You Did It!
You now understand the four magical SEO files in Next.js:
- ImageResponse API - Creates beautiful preview images with code
- robots.ts - Tells search engines what they can and can’t see
- sitemap.ts - Gives search engines a map of your website
- generateSitemaps - Splits big maps into smaller pieces
These helpers work together to make sure Google (and your users) can find, preview, and explore your website easily.
Remember: A well-mapped website is a well-visited website! 🗺️✨
