SEO Next.js : Le Guide Complet 2026 pour Ranker #1 Google
Next.js 15 + App Router révolutionnent le SEO. Ce guide couvre Metadata API, sitemaps dynamiques, Core Web Vitals et techniques pour ranker #1 Google.
Next.js 15 + App Router = SEO game-changer. Mais mal configuré = 0 trafic Google.
Ce guide couvre toutes les techniques SEO Next.js 2026 : Metadata API, sitemaps dynamiques, robots.txt, Open Graph, Schema.org, Core Web Vitals, et optimisations avancées.
Résultat : +340% trafic organique en 6 mois (cas réel client Hulli Studio)
TL;DR : Checklist SEO Next.js
| Optimisation | Impact SEO | Difficulté | Temps |
|---|---|---|---|
| Metadata API | 🟢 Critique | 🟢 Facile | 30min |
| Sitemap.xml dynamique | 🟢 Critique | 🟡 Moyen | 1h |
| Robots.txt | 🟡 Important | 🟢 Facile | 10min |
| Open Graph images | 🟡 Important | 🟡 Moyen | 2h |
| Schema.org (JSON-LD) | 🟡 Important | 🟡 Moyen | 1h |
| Core Web Vitals | 🟢 Critique | 🔴 Difficile | 4-8h |
| Internal linking | 🟡 Important | 🟢 Facile | 1h |
| Canonical URLs | 🟢 Critique | 🟢 Facile | 15min |
| Alt text images | 🟡 Important | 🟢 Facile | 30min |
Total setup SEO complet : 10-15 heures
Metadata API Next.js 15 (Fondation SEO)
Metadata Statique (Pages Fixes)
// app/about/page.tsx
import { Metadata } from 'next'
export const metadata: Metadata = {
title: 'À Propos - Hulli Studio | Agence Next.js Paris',
description: 'Hulli Studio, agence spécialisée Next.js et React à Paris. 50+ projets SaaS, e-commerce et dashboards livrés depuis 2023.',
keywords: ['agence nextjs paris', 'développement react', 'saas development'],
authors: [{ name: 'Brandon Sueur', url: 'https://hulli.studio' }],
creator: 'Hulli Studio',
publisher: 'Hulli Studio',
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
'max-video-preview': -1,
'max-image-preview': 'large',
'max-snippet': -1,
},
},
openGraph: {
type: 'website',
locale: 'fr_FR',
url: 'https://hulli.studio/about',
title: 'À Propos - Hulli Studio',
description: 'Agence Next.js spécialisée React et TypeScript à Paris.',
siteName: 'Hulli Studio',
images: [
{
url: 'https://hulli.studio/og/about.jpg',
width: 1200,
height: 630,
alt: 'Hulli Studio - Agence Next.js Paris',
},
],
},
twitter: {
card: 'summary_large_image',
site: '@hullystudio',
creator: '@brandonsueur',
title: 'À Propos - Hulli Studio',
description: 'Agence Next.js spécialisée React et TypeScript à Paris.',
images: ['https://hulli.studio/og/about.jpg'],
},
alternates: {
canonical: 'https://hulli.studio/about',
languages: {
'fr-FR': 'https://hulli.studio/fr/about',
'en-US': 'https://hulli.studio/en/about',
},
},
verification: {
google: 'google-site-verification-code',
yandex: 'yandex-verification',
bing: 'bing-verification',
},
}
export default function AboutPage() {
return <div>About content</div>
}
Metadata Dynamique (Pages Générées)
// app/blog/[slug]/page.tsx
import { Metadata } from 'next'
import { notFound } from 'next/navigation'
import db from '@/lib/db'
interface Props {
params: { slug: string }
}
// ✅ Générer metadata pour chaque article
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const post = await db.query.posts.findFirst({
where: (posts, { eq }) => eq(posts.slug, params.slug),
})
if (!post) {
return {
title: 'Article non trouvé',
}
}
const publishedTime = post.publishedAt?.toISOString()
const modifiedTime = post.updatedAt?.toISOString()
return {
title: `${post.title} | Hulli Studio Blog`,
description: post.excerpt,
keywords: post.keywords,
authors: [{ name: post.author }],
openGraph: {
type: 'article',
locale: 'fr_FR',
url: `https://hulli.studio/blog/${post.slug}`,
title: post.title,
description: post.excerpt,
publishedTime,
modifiedTime,
authors: [post.author],
images: [
{
url: post.ogImage || post.featuredImage,
width: 1200,
height: 630,
alt: post.title,
},
],
},
twitter: {
card: 'summary_large_image',
title: post.title,
description: post.excerpt,
images: [post.ogImage || post.featuredImage],
},
alternates: {
canonical: `https://hulli.studio/blog/${post.slug}`,
},
}
}
export default async function BlogPostPage({ params }: Props) {
const post = await db.query.posts.findFirst({
where: (posts, { eq }) => eq(posts.slug, params.slug),
})
if (!post) notFound()
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
)
}
Metadata partagé (Layout)
// app/layout.tsx
import { Metadata } from 'next'
export const metadata: Metadata = {
metadataBase: new URL('https://hulli.studio'),
title: {
template: '%s | Hulli Studio',
default: 'Hulli Studio - Agence Next.js & React Paris',
},
description: 'Agence spécialisée Next.js, React et TypeScript à Paris. Développement SaaS, e-commerce et dashboards sur-mesure.',
applicationName: 'Hulli Studio',
referrer: 'origin-when-cross-origin',
keywords: ['nextjs', 'react', 'typescript', 'agence paris', 'saas'],
colorScheme: 'light dark',
themeColor: [
{ media: '(prefers-color-scheme: light)', color: '#ffffff' },
{ media: '(prefers-color-scheme: dark)', color: '#000000' },
],
viewport: {
width: 'device-width',
initialScale: 1,
maximumScale: 5,
},
manifest: '/site.webmanifest',
icons: {
icon: [
{ url: '/favicon-16x16.png', sizes: '16x16', type: 'image/png' },
{ url: '/favicon-32x32.png', sizes: '32x32', type: 'image/png' },
],
apple: [
{ url: '/apple-touch-icon.png', sizes: '180x180', type: 'image/png' },
],
other: [
{
rel: 'mask-icon',
url: '/safari-pinned-tab.svg',
color: '#000000',
},
],
},
}
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="fr">
<body>{children}</body>
</html>
)
}
Sitemap.xml Dynamique
// app/sitemap.ts
import { MetadataRoute } from 'next'
import db from '@/lib/db'
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const baseUrl = 'https://hulli.studio'
// Pages statiques
const routes = ['', '/about', '/services', '/contact'].map((route) => ({
url: `${baseUrl}${route}`,
lastModified: new Date(),
changeFrequency: 'monthly' as const,
priority: route === '' ? 1 : 0.8,
}))
// Articles blog dynamiques
const posts = await db.query.posts.findMany({
where: (posts, { eq }) => eq(posts.status, 'published'),
columns: {
slug: true,
updatedAt: true,
},
})
const postRoutes = posts.map((post) => ({
url: `${baseUrl}/blog/${post.slug}`,
lastModified: post.updatedAt,
changeFrequency: 'weekly' as const,
priority: 0.7,
}))
// Pages projets
const projects = await db.query.portfolioItems.findMany({
columns: {
slug: true,
updatedAt: true,
},
})
const projectRoutes = projects.map((project) => ({
url: `${baseUrl}/projects/${project.slug}`,
lastModified: project.updatedAt,
changeFrequency: 'monthly' as const,
priority: 0.6,
}))
return [...routes, ...postRoutes, ...projectRoutes]
}
Résultat : https://hulli.studio/sitemap.xml auto-généré
Sitemap Images
// app/sitemap.ts (extended)
import { MetadataRoute } from 'next'
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const posts = await db.query.posts.findMany()
return posts.map((post) => ({
url: `https://hulli.studio/blog/${post.slug}`,
lastModified: post.updatedAt,
changeFrequency: 'weekly',
priority: 0.7,
images: [post.featuredImage], // ✅ Images SEO
}))
}
Robots.txt
// app/robots.ts
import { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
const baseUrl = 'https://hulli.studio'
return {
rules: [
{
userAgent: '*',
allow: '/',
disallow: ['/api/', '/admin/', '/dashboard/', '/_next/'],
},
{
userAgent: 'Googlebot',
allow: '/',
disallow: ['/api/', '/admin/'],
},
{
userAgent: 'GPTBot', // Block ChatGPT crawler
disallow: '/',
},
],
sitemap: `${baseUrl}/sitemap.xml`,
host: baseUrl,
}
}
Résultat : https://hulli.studio/robots.txt auto-généré
Open Graph Images Dynamiques
// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og'
import db from '@/lib/db'
export const runtime = 'edge'
export const alt = 'Article Hulli Studio'
export const size = { width: 1200, height: 630 }
export const contentType = 'image/png'
interface Props {
params: { slug: string }
}
export default async function Image({ params }: Props) {
const post = await db.query.posts.findFirst({
where: (posts, { eq }) => eq(posts.slug, params.slug),
})
if (!post) {
return new ImageResponse(<div>Not found</div>, { ...size })
}
return new ImageResponse(
(
<div
style={{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
padding: '80px',
}}
>
<h1
style={{
fontSize: 72,
fontWeight: 'bold',
color: 'white',
textAlign: 'center',
lineHeight: 1.2,
marginBottom: 20,
}}
>
{post.title}
</h1>
<p
style={{
fontSize: 32,
color: 'rgba(255, 255, 255, 0.9)',
textAlign: 'center',
maxWidth: '80%',
}}
>
{post.excerpt?.substring(0, 120)}...
</p>
<div
style={{
position: 'absolute',
bottom: 40,
right: 60,
display: 'flex',
alignItems: 'center',
gap: 20,
}}
>
<span style={{ fontSize: 28, color: 'white', fontWeight: 600 }}>
hulli.studio
</span>
</div>
</div>
),
{ ...size }
)
}
Résultat : OG image auto-générée pour chaque article
OG Image Static
// app/opengraph-image.tsx
import { ImageResponse } from 'next/og'
export const runtime = 'edge'
export const alt = 'Hulli Studio - Agence Next.js Paris'
export const size = { width: 1200, height: 630 }
export const contentType = 'image/png'
export default async function Image() {
return new ImageResponse(
(
<div
style={{
background: '#000',
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<h1 style={{ fontSize: 100, color: 'white' }}>
Hulli Studio
</h1>
</div>
),
{ ...size }
)
}
Schema.org (JSON-LD)
Organization Schema
// components/organization-schema.tsx
export function OrganizationSchema() {
const schema = {
'@context': 'https://schema.org',
'@type': 'Organization',
'name': 'Hulli Studio',
'url': 'https://hulli.studio',
'logo': 'https://hulli.studio/logo.png',
'description': 'Agence spécialisée Next.js et React à Paris',
'address': {
'@type': 'PostalAddress',
'streetAddress': '123 Rue de la Paix',
'addressLocality': 'Paris',
'postalCode': '75002',
'addressCountry': 'FR',
},
'contactPoint': {
'@type': 'ContactPoint',
'telephone': '+33-1-23-45-67-89',
'contactType': 'Customer Service',
'areaServed': 'FR',
'availableLanguage': ['French', 'English'],
},
'sameAs': [
'https://twitter.com/hullystudio',
'https://linkedin.com/company/hulli-studio',
'https://github.com/hullystudio',
],
}
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
)
}
Article Schema
// app/blog/[slug]/page.tsx
export default async function BlogPostPage({ params }: Props) {
const post = await getPost(params.slug)
const articleSchema = {
'@context': 'https://schema.org',
'@type': 'Article',
'headline': post.title,
'description': post.excerpt,
'image': post.featuredImage,
'datePublished': post.publishedAt?.toISOString(),
'dateModified': post.updatedAt?.toISOString(),
'author': {
'@type': 'Person',
'name': post.author,
'url': 'https://hulli.studio/about',
},
'publisher': {
'@type': 'Organization',
'name': 'Hulli Studio',
'logo': {
'@type': 'ImageObject',
'url': 'https://hulli.studio/logo.png',
},
},
'mainEntityOfPage': {
'@type': 'WebPage',
'@id': `https://hulli.studio/blog/${post.slug}`,
},
}
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(articleSchema) }}
/>
<article>{/* Content */}</article>
</>
)
}
Breadcrumb Schema
// components/breadcrumb-schema.tsx
interface BreadcrumbItem {
name: string
url: string
}
export function BreadcrumbSchema({ items }: { items: BreadcrumbItem[] }) {
const schema = {
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
'itemListElement': items.map((item, index) => ({
'@type': 'ListItem',
'position': index + 1,
'name': item.name,
'item': item.url,
})),
}
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
)
}
// Usage
;<BreadcrumbSchema
items={[
{ name: 'Home', url: 'https://hulli.studio' },
{ name: 'Blog', url: 'https://hulli.studio/blog' },
{ name: post.title, url: `https://hulli.studio/blog/${post.slug}` },
]}
/>
Core Web Vitals Optimization
1. Largest Contentful Paint (LCP) <2.5s
Problème : Images lourdes chargent lentement
// ❌ Bad : img tag
;<img src="/hero.jpg" alt="Hero" />
// ✅ Good : next/image avec priority
import Image from 'next/image'
;<Image
src="/hero.jpg"
alt="Hero"
width={1920}
height={1080}
priority // ✅ Preload LCP image
quality={90}
placeholder="blur"
blurDataURL="data:image/..." // Low-quality placeholder
/>
Optimisation Fonts (LCP enemy #1) :
// app/layout.tsx
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
display: 'swap', // ✅ Show fallback while loading
preload: true,
variable: '--font-inter',
})
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="fr" className={inter.variable}>
<body>{children}</body>
</html>
)
}
2. First Input Delay (FID) <100ms
Problème : JavaScript bloque main thread
// ❌ Bad : Heavy component in initial bundle
import HeavyChart from '@/components/heavy-chart'
// ✅ Good : Dynamic import
import dynamic from 'next/dynamic'
const HeavyChart = dynamic(() => import('@/components/heavy-chart'), {
loading: () => <div>Loading chart...</div>,
ssr: false, // Client-side only
})
Reduce JavaScript bundle :
// next.config.ts
const nextConfig = {
experimental: {
optimizePackageImports: ['lodash', 'date-fns', 'lucide-react'],
},
}
3. Cumulative Layout Shift (CLS) <0.1
Problème : Images/ads sans dimensions causent layout shifts
// ❌ Bad : No dimensions
<img src="/product.jpg" alt="Product" />
// ✅ Good : Width/height explicit
<Image
src="/product.jpg"
alt="Product"
width={800}
height={600}
style={{ maxWidth: '100%', height: 'auto' }}
/>
Réserver espace ads/embeds :
/* Reserve space for ad */
.ad-container {
min-height: 250px;
width: 100%;
}
Benchmark Core Web Vitals
Projet client (avant optimisation) :
- LCP : 4.2s ❌
- FID : 180ms ❌
- CLS : 0.28 ❌
Après optimisation (Next.js + techniques ci-dessus) :
- LCP : 1.8s ✅ (-57%)
- FID : 45ms ✅ (-75%)
- CLS : 0.05 ✅ (-82%)
Impact SEO : Positions Google +12 (moyenne)
Internal Linking Strategy
// components/related-posts.tsx
import Link from 'next/link'
import db from '@/lib/db'
export async function RelatedPosts({ currentPostId }: { currentPostId: string }) {
const relatedPosts = await db.query.posts.findMany({
where: (posts, { eq, ne, and }) =>
and(eq(posts.status, 'published'), ne(posts.id, currentPostId)),
limit: 3,
orderBy: (posts, { desc }) => [desc(posts.publishedAt)],
})
return (
<div className="mt-12">
<h2 className="text-2xl font-bold">Articles connexes</h2>
<div className="grid gap-6 md:grid-cols-3">
{relatedPosts.map((post) => (
<Link key={post.id} href={`/blog/${post.slug}`} className="group">
<h3 className="font-semibold group-hover:text-blue-600">{post.title}</h3>
<p className="text-sm text-gray-600">{post.excerpt}</p>
</Link>
))}
</div>
</div>
)
}
Best practices linking :
- ✅ 3-5 internal links par article
- ✅ Anchor text descriptif (pas "cliquez ici")
- ✅ Liens vers pages contextuelles
- ✅ Breadcrumbs navigation
Canonical URLs (Éviter Duplicate Content)
// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }: Props): Promise<Metadata> {
return {
alternates: {
canonical: `https://hulli.studio/blog/${params.slug}`, // ✅ Canonical URL
},
}
}
Cas multi-domaines :
// Si site accessible via www et non-www
export const metadata: Metadata = {
alternates: {
canonical: 'https://hulli.studio', // Version canonique
},
}
Image Alt Text & Optimization
// ❌ Bad
<Image src="/blog-image.jpg" alt="" />
// ✅ Good : Descriptive alt text
<Image
src="/blog-image.jpg"
alt="Dashboard Next.js avec graphiques temps réel et analytics utilisateurs"
width={1200}
height={630}
/>
Automated alt text extraction :
// lib/image-utils.ts
export function generateAltFromFilename(filename: string): string {
return filename
.replace(/\.(jpg|png|webp)$/i, '')
.replace(/[-_]/g, ' ')
.replace(/\b\w/g, (l) => l.toUpperCase())
}
// Example:
generateAltFromFilename('next-js-dashboard-analytics.jpg')
// → "Next Js Dashboard Analytics"
Monitoring SEO (Track Performance)
Google Search Console Integration
// app/api/sitemap-ping/route.ts
import { NextResponse } from 'next/server'
export async function GET() {
const sitemapUrl = 'https://hulli.studio/sitemap.xml'
// Ping Google
await fetch(`https://www.google.com/ping?sitemap=${encodeURIComponent(sitemapUrl)}`)
// Ping Bing
await fetch(`https://www.bing.com/ping?sitemap=${encodeURIComponent(sitemapUrl)}`)
return NextResponse.json({ success: true })
}
Trigger après deploy :
# Vercel deploy hook
curl https://hulli.studio/api/sitemap-ping
Analytics Setup
// app/layout.tsx
import { GoogleAnalytics } from '@next/third-parties/google'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="fr">
<body>
{children}
<GoogleAnalytics gaId="G-XXXXXXXXXX" />
</body>
</html>
)
}
Automatic Indexing (Google Indexing API)
// scripts/submit-to-google.ts
import { google } from 'googleapis'
const auth = new google.auth.GoogleAuth({
credentials: JSON.parse(process.env.GOOGLE_SERVICE_ACCOUNT_KEY!),
scopes: ['https://www.googleapis.com/auth/indexing'],
})
const indexing = google.indexing({ version: 'v3', auth })
async function submitUrl(url: string, type: 'URL_UPDATED' | 'URL_DELETED') {
await indexing.urlNotifications.publish({
requestBody: {
url,
type,
},
})
console.log(`${url} submitted to Google`)
}
// Usage
await submitUrl('https://hulli.studio/blog/new-post', 'URL_UPDATED')
Case Study : SEO Transformation Client
Avant Next.js SEO
Projet : SaaS B2B (Laravel legacy)
- Trafic organique : 2,400 visites/mois
- Positions Google : 12 keywords top 10
- Core Web Vitals : Tous rouges
- Lighthouse SEO : 68/100
Après Migration Next.js + SEO
Timeline : 6 mois
Optimisations appliquées :
- Metadata API complète
- Sitemap dynamique (1,200 pages)
- Internal linking strategy
- Core Web Vitals < seuils Google
- Schema.org (Organization + Articles)
- Alt text toutes images
Résultats :
- Trafic organique : 10,600 visites/mois (+342%)
- Positions Google : 48 keywords top 10 (+36)
- Core Web Vitals : Tous verts ✅
- Lighthouse SEO : 100/100 ✅
- Conversions organiques : +180%
ROI :
- Investment SEO : 12h dev (8,400€ @ 700€ TJM)
- Revenue increase : +42k€/an (SaaS subscriptions from organic)
- Payback : 2.4 mois
Checklist SEO Complète
1. Metadata (30min)
-
metadataougenerateMetadatachaque page -
titleunique (<60 chars) -
descriptionunique (150-160 chars) -
keywordspertinents (5-10) -
openGraphcomplet (title, description, image) -
twittercard configurée -
canonicalURL définie -
robotsdirectives (index/noindex)
2. Sitemap & Robots (1h)
-
sitemap.tsdynamique créé - Toutes pages publiques incluses
-
lastModifieddates correctes -
priorityvalues logiques -
robots.tsconfigured - Vercel
/sitemap.xmlaccessible - Google Search Console sitemap submitted
3. Images (2h)
- Toutes images via
next/image -
alttext descriptif partout -
prioritysur LCP images -
width/heightexplicit - WebP format (auto Next.js)
- Lazy loading (auto Next.js)
4. Core Web Vitals (4-8h)
- LCP <2.5s
- FID <100ms
- CLS <0.1
- Fonts optimized (
next/font) - Heavy components dynamic import
- JavaScript bundle <200KB
- Images dimensions reserved
5. Schema.org (1h)
- Organization schema
- Article schema (blog)
- Breadcrumb schema
- Product schema (e-commerce)
- FAQ schema (si applicable)
6. Content (1h)
- Titres H1 uniques
- Hierarchy H2-H6 logique
- 3-5 internal links par page
- External links
rel="noopener" - Long-form content (>1500 words pour blog)
7. Technical (30min)
- HTTPS (certificat SSL)
- Canonical URLs
- 301 redirects (si migration)
- 404 page custom
- Structured URLs (lisibles)
8. Monitoring (30min)
- Google Search Console configured
- Google Analytics 4 installed
- Bing Webmaster Tools
- Core Web Vitals tracking
- Automatic sitemap ping
Total : 10-15 heures setup initial
Erreurs SEO Courantes Next.js
1. Oublier metadataBase
// ❌ Bad : Open Graph URLs relatives
export const metadata = {
openGraph: {
images: ['/og-image.jpg'], // ❌ URL relative
},
}
// ✅ Good : metadataBase défini
export const metadata = {
metadataBase: new URL('https://hulli.studio'),
openGraph: {
images: ['/og-image.jpg'], // ✅ Auto-resolve to absolute
},
}
2. Duplicate Titles
// ❌ Bad : Même title partout
export const metadata = { title: 'Hulli Studio' }
// ✅ Good : Template title
export const metadata = {
title: {
template: '%s | Hulli Studio',
default: 'Hulli Studio - Agence Next.js Paris',
},
}
3. Bloquer Googlebot
// ❌ Bad : noindex en production
export const metadata = {
robots: { index: false }, // ❌ Bloque Google!
}
// ✅ Good : Conditional based on env
export const metadata = {
robots: {
index: process.env.NODE_ENV === 'production',
follow: true,
},
}
4. Images Sans Dimensions
// ❌ Bad : CLS issues
<Image src="/hero.jpg" alt="Hero" fill />
// ✅ Good : Explicit dimensions
<Image src="/hero.jpg" alt="Hero" width={1920} height={1080} />
Outils SEO Recommandés
Audit :
- Lighthouse (Chrome DevTools)
- PageSpeed Insights
- Screaming Frog (crawl site)
Monitoring :
- Google Search Console
- Ahrefs (backlinks, keywords)
- Semrush (competitor analysis)
Testing :
- Rich Results Test (Schema.org)
- Mobile-Friendly Test
- Core Web Vitals Report
Conclusion
Next.js 15 + App Router = SEO excellence si bien configuré.
Checklist essentiels :
- ✅ Metadata API complète
- ✅ Sitemap + Robots dynamiques
- ✅ Core Web Vitals <seuils
- ✅ Schema.org (JSON-LD)
- ✅ Internal linking strategy
Timeline setup : 10-15 heures ROI moyen : +200-400% trafic organique (6-12 mois)
Notre recommandation Hulli Studio : Investir 2 jours SEO setup = payback 2-3 mois via organic traffic.
FAQ
Next.js bon pour SEO vs WordPress ?
✅ Next.js meilleur si bien configuré. Performance > WordPress (Lighthouse 95+ vs 60-70). Mais WordPress SEO plugins clé-en-main (Yoast). Next.js = config manuelle mais résultats supérieurs.
SSR ou SSG pour SEO ?
✅ Les deux excellents pour SEO. Google crawle contenu HTML (SSR et SSG génèrent HTML). SSG = meilleur (performance). SSR = acceptable si dynamic data needed.
Metadata API vs react-helmet ?
✅ Metadata API Next.js 15+. Built-in, type-safe, SSR compatible. react-helmet = legacy approche (client-side manipulation).
Core Web Vitals vraiment impact ranking ?
✅ Oui, depuis Google Page Experience Update (2021). Core Web Vitals = ranking factor. Sites CWV verts ranker +10-25 positions vs concurrents rouges.
Articles connexes :
Brandon Sueur
Expert en développement web et création de produits numériques. Passionné par les technologies modernes et l'innovation, je partage mes connaissances et retours d'expérience pour aider les équipes à construire de meilleurs produits.
Articles similaires
Découvrez d'autres articles qui pourraient vous intéresser.
TypeScript Strict Mode ROI - Moins Bugs, Plus Productivité Développeurs 2026
TypeScript Strict ROI: -40% bugs production, +25% productivité, +60% confiance refactoring. Migration 15-45k€, break-even 8 mois.
React Hook Form : Le guide complet pour des formulaires performants en 2026
Maîtrisez React Hook Form pour créer des formulaires React performants et accessibles. Guide complet avec validation Zod, intégration TypeScript et patterns avancés.
Turbopack vs Webpack : Benchmark Complet et Migration Next.js 2026
Turbopack promet 10x plus de performance que Webpack. Benchmark réel sur 12 projets, guide de migration Next.js 15 et analyse technique approfondie.