Canonical URLs & Redirects
Canonical URLs and redirects are essential for preventing duplicate content issues and maintaining a clean URL structure.
What are Canonical URLs?
A canonical URL tells search engines which version of a page is the "main" one. This is essential for:
- Preventing duplicate content penalties
- Consolidating link equity
- Handling www vs non-www, HTTP vs HTTPS
- Managing query parameter variations
Setting Canonical URLs in Next.js
export const metadata: Metadata = {
alternates: {
canonical: 'https://example.com/blog/seo-guide',
},
}
Dynamic Canonical URLs
For dynamic routes, build the canonical URL from the slug:
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params
return {
alternates: {
canonical: `https://example.com/blog/${slug}`,
},
}
}
Important Rules
- Always use absolute URLs - Include the full domain
- Be consistent - Use the same format everywhere (with or without www)
- Match your preference - Canonical should match your preferred URL format
Types of Redirects
| Code | Type | When to Use |
|---|---|---|
| 301 | Permanent | URL has permanently moved |
| 302 | Temporary | URL temporarily redirected |
| 307 | Temporary | Preserves HTTP method |
| 308 | Permanent | Preserves HTTP method |
301 vs 308
- 301 - May change POST to GET (legacy behavior)
- 308 - Preserves the HTTP method (modern)
Next.js uses 308 for permanent redirects by default.
Configuring Redirects in next.config.js
// next.config.js
module.exports = {
async redirects() {
return [
// Simple redirect
{
source: '/old-page',
destination: '/new-page',
permanent: true, // 308 status code
},
// Redirect with path parameters
{
source: '/blog/:slug',
destination: '/articles/:slug',
permanent: true,
},
// Redirect with wildcard
{
source: '/docs/:path*',
destination: '/documentation/:path*',
permanent: true,
},
]
},
}
Query Parameter Redirects
{
source: '/search',
has: [
{
type: 'query',
key: 'q',
},
],
destination: '/results?query=:q',
permanent: false,
}
Domain Redirects
{
source: '/:path*',
has: [
{
type: 'host',
value: 'old-domain.com',
},
],
destination: 'https://new-domain.com/:path*',
permanent: true,
}
Programmatic Redirects
For dynamic redirects based on logic:
// app/old-page/page.tsx
import { redirect } from 'next/navigation'
export default function OldPage() {
redirect('/new-page')
}
// Conditional redirect
export default async function Page({ params }) {
const { slug } = await params
const exists = await checkIfExists(slug)
if (!exists) {
redirect('/not-found')
}
return <div>Content</div>
}
Handling Trailing Slashes
Consistency with trailing slashes prevents duplicate content:
// next.config.js
module.exports = {
trailingSlash: true, // /about/ instead of /about
// or
trailingSlash: false, // /about instead of /about/
}
Choose one approach and stick with it. Next.js will automatically redirect to the correct format.
URL Best Practices
Good URLs
/courses/seo-nextjs
/blog/how-to-optimize-images
/products/wireless-headphones
Bad URLs
/courses?id=123
/blog/post?slug=xyz
/p/12345
URL Guidelines
- Use hyphens to separate words (not underscores)
- Keep URLs short but descriptive
- Use lowercase letters only
- Include keywords when natural
- Avoid parameters when possible
- Be consistent with trailing slashes
Custom 404 Pages
Handle missing pages gracefully:
// app/not-found.tsx
import Link from 'next/link'
export default function NotFound() {
return (
<div>
<h1>Page Not Found</h1>
<p>Sorry, we couldn't find the page you're looking for.</p>
<Link href="/">Go back home</Link>
</div>
)
}
Triggering 404
import { notFound } from 'next/navigation'
export default async function Page({ params }) {
const { slug } = await params
const post = await getPost(slug)
if (!post) {
notFound() // Renders not-found.tsx
}
return <article>{post.content}</article>
}
Redirect Best Practices
Do
- Use 301/308 for permanent moves - Passes link equity
- Update internal links - Don't rely solely on redirects
- Monitor for 404s - Redirect discovered broken links
- Document redirects - Keep track of what redirects where
Don't
- Avoid redirect chains - A → B → C is bad; A → C is better
- Don't redirect to redirects - Creates chains
- Don't have too many - Can slow down builds
- Don't forget old URLs - Maintain redirects for years
Summary
In this lesson, you learned:
- Canonical URLs and why they matter
- Setting canonical URLs statically and dynamically
- Redirect types (301, 302, 307, 308)
- Configuring redirects in next.config.js
- Programmatic redirects with redirect()
- Trailing slash consistency
- URL structure best practices
- Custom 404 pages
In the next module, we'll cover performance optimization and Core Web Vitals.

