Next.js 16 Uygulama Yönlendiricisi: Üretim Modelleri ve Tuzaklar
Next.js Uygulama Yönlendiricisi, React uygulamalarını oluşturma şeklimizi temelden değiştirdi ve Next.js 16 ile birlikte kalıplar gerçekten üretime hazır hale geldi. Ancak Pages Router'dan kavramsal değişim oldukça diktir; sunucu bileşenleri, kısmi ön işleme, iç içe yerleşimler ve tamamen yeniden tasarlanmış bir veri getirme modelinin tümü, belgelerin tek başına tam olarak aktaramayacağı zihinsel modelin yeniden yapılandırılmasını gerektirir.
Bu kılavuz, üretimde gerçekten önemli olan modelleri kapsar: önbellek zehirlenmesini önleyen, gereksiz istemci paketlerini ortadan kaldıran ve Önemli Web Verilerinizi uygun ölçekte yeşil tutan modeller. 11 yerel ayarlı i18n, dinamik OG görüntüleri ve 5.577 MDX içerik dosyası için sunucu tarafı işlemeyle 249 sayfalık bir Next.js 16 uygulaması oluşturma deneyiminden yararlanacağız.
Önemli Çıkarımlar
- Yerel ayarlara duyarlı SEO gerektiren sayfalarda asla
export const metadatakullanmayın; her zamangenerateMetadata()kullanın- Sunucu Bileşenleri varsayılandır;
'use client'yalnızca gerçekten etkileşime ihtiyacınız olduğunda ekleyinloading.tsxveerror.tsxkökte değil rota segmentleriyle aynı yerde bulunurproxy.tsdosyası Next.js 16'nın ara yazılımıdır — hemmiddleware.tshem deproxy.tsoluşturmayın- Sunucu Bileşenlerindeki
fetch(), aynı işleme içindeki aynı istekleri otomatik olarak tekilleştirirunstable_cacheetiketli, her şeyi yeniden doğrulamadan cerrahi önbellek geçersiz kılmayı sağlar- Derleme sırasında statik olarak oluşturmak istediğiniz dinamik rotalar için
generateStaticParams()gereklidir- JSON-LD yapılandırılmış verilerinin, unicode kodlamayı kullanarak komut dosyası aralarını temizlemesi gerekir
Sunucu Bileşeni Zihinsel Modeli
Geliştiricilerin Uygulama Yönlendiricisi ile yaptığı en büyük hata, 'use client''a çok erken ulaşmaktır. Next.js 16'da app/ dizinindeki her bileşen varsayılan olarak bir Sunucu Bileşenidir. Bu doğru varsayılandır; Sunucu Bileşenleri sunucuda oluşturulur, sıfır JavaScript paketi etkisine sahiptir ve veritabanlarına ve API'lere doğrudan erişebilir.
Kural: 'use client' öğesini bileşen ağacınızın yapraklarına itin. Bir sayfa, verileri alıp etkileşimleri yöneten ince İstemci Bileşeni kabuğuna aktaran bir Sunucu Bileşeni olabilir.
// app/[locale]/blog/[slug]/page.tsx — Server Component (no directive needed)
import { getBlogPost } from '@/lib/blog';
import { BlogContent } from '@/components/blog/blog-content'; // Client Component
export default async function BlogPostPage({
params,
}: {
params: Promise<{ slug: string; locale: string }>;
}) {
const { slug, locale } = await params;
const post = await getBlogPost(slug, locale);
if (!post) notFound();
return (
<article>
<h1>{post.title}</h1>
{/* Server Component — no JS sent to browser */}
<PostMeta author={post.author} date={post.date} />
{/* Client Component — only this file sent as JS */}
<BlogContent content={post.content} />
</article>
);
}
// components/blog/blog-content.tsx — Client Component
'use client';
import { useState } from 'react';
export function BlogContent({ content }: { content: string }) {
const [expanded, setExpanded] = useState(false);
return (
<div>
<div>{content}</div>
{/* Client-side interactivity lives here */}
</div>
);
}
Temel bilgi: BlogContent tarayıcıya JavaScript gönderir. PostMeta bunu yapmaz. Gereksiz her 'use client' yönergesi paketinizi şişirir.
Statik Meta Veri Üzerinden createdMetadata
Statik export const metadata basitliği nedeniyle caziptir, ancak yerel ayara duyarlı olamaz, rota parametrelerine erişemez ve veri getiremez. Herhangi bir gerçek uygulama için generateMetadata() tek doğru seçimdir:
// app/[locale]/blog/[slug]/page.tsx
import type { Metadata } from 'next';
import { getTranslations } from 'next-intl/server';
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string; locale: string }>;
}): Promise<Metadata> {
const { slug, locale } = await params;
const post = await getBlogPost(slug, locale);
if (!post) return {};
const locales = ['en', 'zh', 'es', 'ar', 'pt', 'fr', 'de', 'ja', 'tr', 'hi', 'ur'];
return {
title: post.title,
description: post.description,
alternates: {
canonical: `https://ecosire.com/${locale === 'en' ? '' : locale + '/'}blog/${slug}`,
languages: Object.fromEntries(
locales.map((loc) => [
loc,
`https://ecosire.com/${loc === 'en' ? '' : loc + '/'}blog/${slug}`,
])
),
},
openGraph: {
title: post.title,
description: post.description,
images: [`/api/og/blog/${slug}?locale=${locale}`],
type: 'article',
publishedTime: post.date,
},
};
}
alternates.languages alanı her yerel ayar için hreflang etiketleri oluşturur; bu, çok dilli SEO için gereklidir. Arama motorları bunları her kullanıcıya doğru dil sürümünü sunmak için kullanır.
İç İçe Düzenler ve Paylaşılan Durum
Uygulama Yönlendiricinin iç içe düzen sistemi güçlüdür ancak bariz olmayan bir sınırlaması vardır: düzenler, verileri destek yoluyla alt öğelerine aktaramaz. Geçici çözüm, React Context (yalnızca İstemci Bileşenleri) veya verileri her düzeyde bağımsız olarak getirmektir.
app/
[locale]/
layout.tsx <- Locale layout: fonts, i18n provider, theme
page.tsx <- Homepage
blog/
layout.tsx <- Blog layout: sidebar, breadcrumbs
page.tsx <- Blog list
[slug]/
layout.tsx <- Post layout: reading progress, TOC
page.tsx <- Post content
loading.tsx <- Skeleton while post loads
error.tsx <- Error boundary for this segment
Her layout.tsx alt öğelerini bağımsız olarak sarar. Ana düzende getirilen veriler çocukların kullanımına otomatik olarak sunulmaz; veriye ihtiyaç duyan her bileşen, verileri bağımsız olarak getirir. Next.js 16, bir oluşturma döngüsü içinde aynı fetch() çağrılarını tekilleştirir, dolayısıyla bu, göründüğü kadar pahalı değildir.
// app/[locale]/layout.tsx
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
export default async function LocaleLayout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
const messages = await getMessages();
return (
<html lang={locale} dir={['ar', 'ur'].includes(locale) ? 'rtl' : 'ltr'}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
Önbelleğe Alma Stratejisi: Dört Katman
Next.js 16'nın dört farklı önbellekleme katmanı vardır. Bir sayfanın neden güncellenip güncellenmediğini anlamak için dördünü de anlamak gerekir:
1. Not Alma İsteği — Bir oluşturma döngüsü içindeki aynı fetch() URL'si bir kez çağrılır. Otomatik, yapılandırma yok.
2. Veri Önbelleği — İstekler arasında kalıcıdır. fetch() yanıtları varsayılan olarak süresiz olarak önbelleğe alınır.
// Force revalidation every hour
const data = await fetch('/api/posts', { next: { revalidate: 3600 } });
// Never cache (equivalent to SSR)
const data = await fetch('/api/posts', { cache: 'no-store' });
3. Tam Rota Önbelleği — Statik rotalar için oluşturma sırasında önbelleğe alınan statik HTML ve RSC yükü.
4. Yönlendirici Önbelleği — Ziyaret edilen rotaların istemci tarafı önbelleği. Tarayıcı oturumu boyunca devam eder.
Veritabanı sorguları için (getirilmeyen), unstable_cache kullanın:
import { unstable_cache } from 'next/cache';
const getCachedBlogPosts = unstable_cache(
async (locale: string) => {
return db.select().from(posts).where(eq(posts.locale, locale));
},
['blog-posts'], // Cache key
{
revalidate: 3600, // Revalidate hourly
tags: ['blog'], // Tag for manual invalidation
}
);
// Invalidate from a Server Action or Route Handler:
import { revalidateTag } from 'next/cache';
revalidateTag('blog'); // All blog-tagged caches cleared
Hata Sınırları: İki Düzey Gereklidir
Üretim uygulamalarının iki hata sınırına ihtiyacı vardır: biri yerel ayarlara duyarlı sayfalar için, diğeri ise kök düzeyindeki hatalar için genel geri dönüş.
// app/[locale]/error.tsx — Locale-level error boundary
'use client';
import { useTranslations } from 'next-intl';
import { useEffect } from 'react';
export default function LocaleError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
const t = useTranslations('errors');
useEffect(() => {
// Log to error tracking service
console.error(error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<h2>{t('title')}</h2>
<p>{t('description')}</p>
<button onClick={reset}>{t('retry')}</button>
</div>
);
}
// app/global-error.tsx — Root fallback (no i18n available here)
'use client';
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
// Cannot use useTranslations here — no NextIntlClientProvider above
// Use inline styles — no Tailwind in root error boundaries
return (
<html>
<body>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', padding: '2rem' }}>
<h2 style={{ fontSize: '1.5rem', fontWeight: 'bold' }}>Something went wrong</h2>
<button
onClick={reset}
style={{ marginTop: '1rem', padding: '0.5rem 1rem', cursor: 'pointer' }}
>
Try again
</button>
</div>
</body>
</html>
);
}
global-error.tsx tetiklendiğinde layout.tsx kökünün yerini alır, dolayısıyla <html> ve <body> etiketlerini içermelidir. Düzende yaşayan içerik sağlayıcılara erişemez; i18n yok, tema yok, kimlik doğrulama bağlamı yok.
Ara yazılım: proxy.ts ve ara katman yazılımı.ts
Next.js, uç ara katman yazılımı için middleware.ts (veya middleware.js) kullanır. next-intl v4 kullanan bir projede, ara katman yazılımı kurulumu sıklıkla proxy.ts olarak yeniden adlandırılır ve yeniden dışa aktarılır, bu da kritik bir tuzak oluşturur: hem middleware.ts hem de proxy.ts oluşturmayın. Her ikisine de sahip olmak derleme hatasına neden olur.
İ18n'yi kimlik doğrulama korumasıyla birleştirmek için doğru model:
// src/proxy.ts — This IS the Next.js middleware
import createMiddleware from 'next-intl/middleware';
import { NextRequest, NextResponse } from 'next/server';
import { routing } from './i18n/routing';
const intlMiddleware = createMiddleware(routing);
const protectedPaths = ['/dashboard', '/portal'];
export default function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Check auth for protected paths
const isProtected = protectedPaths.some((path) =>
pathname.includes(path)
);
if (isProtected) {
const token = request.cookies.get('ecosire_auth');
if (!token) {
const url = request.nextUrl.clone();
url.pathname = '/auth/login';
// Prevent open redirect — never allow // prefix
const redirect = pathname.startsWith('//') ? '/' : pathname;
url.searchParams.set('redirect', redirect);
return NextResponse.redirect(url);
}
}
return intlMiddleware(request);
}
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico|.*\\..*).*)'],
};
Bu tek dosya hem i18n yerel ayar tespitini hem de kimlik doğrulama korumasını yönetir. config.matcher statik dosyaları, API rotalarını ve Next.js dahili öğelerini hariç tutar.
JSON-LD Yapılandırılmış Veri Güvenliği
JSON-LD komut dosyaları yaygın bir XSS vektörüdür. Verileriniz bir kapanış komut dosyası etiketi içeriyorsa komut dosyası bloğunun dışına çıkabilir. Düzeltme zorunludur; küçük karakteri kodlayın:
// components/seo/JsonLd.tsx
interface JsonLdProps {
data: Record<string, unknown>;
}
export function JsonLd({ data }: JsonLdProps) {
// Sanitize script tag breakout attempts by encoding '<' as unicode
const json = JSON.stringify(data).replace(/</g, '\\u003c');
return (
<script
type="application/ld+json"
// Safe: content is JSON-serialized and sanitized above
suppressHydrationWarning
ref={(el) => { if (el) el.textContent = json; }}
/>
);
}
Sayfada kullanım:
<JsonLd
data={{
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: post.title,
description: post.description,
inLanguage: locale,
author: {
'@type': 'Organization',
name: 'ECOSIRE',
},
}}
/>
inLanguage alanı çok dilli SEO için gereklidir; AI tarayıcılarına ve arama motorlarına içeriğin hangi dilde olduğunu söyler ve İngilizce olmayan sorgular için sıralama doğruluğunu artırır.
Dinamik OG Görselleri
Next.js 16'nın ImageResponse API'si uçta sayfa başına OG görüntüleri oluşturur. Blog gönderileri için kurulum:
// app/api/og/blog/[slug]/route.tsx
import { ImageResponse } from 'next/og';
export const runtime = 'edge';
export async function GET(
request: Request,
{ params }: { params: Promise<{ slug: string }> }
) {
const { slug } = await params;
const post = await getPostMeta(slug);
return new ImageResponse(
(
<div
style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'flex-end',
width: '100%',
height: '100%',
background: 'linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 100%)',
padding: '60px',
}}
>
<div style={{ fontSize: 48, fontWeight: 700, color: 'white', lineHeight: 1.2 }}>
{post.title}
</div>
<div style={{ fontSize: 24, color: '#94a3b8', marginTop: 20 }}>
{post.description}
</div>
</div>
),
{ width: 1200, height: 630 }
);
}
Yaygın Tuzaklar ve Çözümler
Tuzak 1: Sunucu Bileşenleri çalışacakken İstemci Bileşenlerindeki verileri almak
Bir bileşen yalnızca verileri görüntülüyorsa ve React durumuna veya tarayıcı API'lerine ihtiyaç duymuyorsa, bunun bir Sunucu Bileşeni olması gerekir. 'use client' eklenmesi, içe aktardığı her şeyi istemci paketine zorlar.
2. Tuzak: Eşzamansız Sunucu Bileşenleri etrafında Eksik Askıya Alma sınırları
// Without Suspense — entire page blocks until data loads
export default async function Page() {
const data = await slowFetch(); // 2 seconds
return <div>{data}</div>;
}
// With Suspense — page streams, slow part shows skeleton
export default function Page() {
return (
<Suspense fallback={<DataSkeleton />}>
<SlowDataComponent />
</Suspense>
);
}
3. Tuzak: Önbelleğe alınmış işlevlerde çerezleri() veya başlıkları() kullanmak
cookies() ve headers(), rotayı dinamik oluşturmaya dahil eden dinamik API'lerdir. Bunları bir unstable_cache geri çağırma içinde kullanmak, önbelleği sessizce bozar.
4. Tuzak: Next.js 16'da parametreleri beklemeyi unutmak
Next.js 16'da params artık bir Promise'dir. await öğesinin unutulması çalışma zamanı hatalarına neden olur:
// Next.js 15 — params was synchronous
export default function Page({ params }: { params: { slug: string } }) {
const { slug } = params;
}
// Next.js 16 — params is async
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params; // Required
}
Sıkça Sorulan Sorular
Ne zaman dinamik oluşturma yerine createdStaticParams() kullanmalıyım?
Sık sık değişen içerik için (blog gönderileri, ürün sayfaları, belgeler) generateStaticParams() kullanın. Bu, sıfır çalışma zamanı maliyetiyle derleme sırasında statik HTML oluşturur. Kişiselleştirilmiş içerik, gerçek zamanlı veriler veya oluşturma süresinin engelleyici olacağı binlerce değişken içeren sayfalar için dinamik oluşturmayı kullanın. Ara sıra değişen içerik için her ikisini de ISR (revalidate) ile birleştirin.
Uygulama Yönlendiricisinde kimlik doğrulamayı nasıl hallederim?
Kimlik doğrulama belirteçlerini HttpOnly çerezlerinde saklayın; asla localStorage veya sessionStorage. Korunan rotalara ilişkin çerez varlığını kontrol etmek için ara yazılım (proxy.ts) kullanın. Sunucu Bileşenleri'nde, next/headers'den cookies() içeren çerezleri okuyun. Kullanıcı verilerini sunucudan istemciye props veya React Context aracılığıyla aktarın; asla İstemci Bileşenlerindeki çerezleri yeniden okuyarak.
loading.tsx ile Suspense arasındaki fark nedir?
loading.tsx, Next.js'nin tüm rota segmenti için otomatik olarak bir Askıya Alma sınırına sardığı özel bir dosyadır. Segmentin eşzamansız içeriği yüklenirken hemen gösterilir. Manuel Suspense sınırları size daha hassas kontrol sağlar; bir sayfanın farklı bölümleri için farklı çerçeveler gösterebilirsiniz. Her ikisi de aynı temel React Suspense mekanizmasını kullanır.
Sunucu ve İstemci Bileşenleri arasında durumu nasıl paylaşırım?
Yapamazsınız — Sunucu Bileşenleri bir kez oluşturulur ve çalışma zamanı durumu yoktur. Verileri sunucudan istemciye sahne donanımı aracılığıyla aktarın. Gezinmelerde devam etmesi gereken istemci tarafı durumu için URL arama parametrelerini (paylaşılabilir durum için), Zustand veya Redux'u (geçici durum için) veya çerezleri (kullanıcı tercihleri için) kullanın. Hiçbir zaman bir İstemci Bileşeninin durumunu bir Sunucu Bileşenine aktarmaya çalışmayın.
Geliştirme için Turbopack'i nasıl kurarım?
Geliştirici betiğinize --turbo ekleyin: "dev": "next dev --turbo". Turbopack, Next.js 16'nın Rust tabanlı paketleyicisidir ve çok daha hızlı soğuk başlatma ve HMR'ye sahiptir. Çoğu proje için üretime hazırdır ancak bazı web paketi eklenti uyumsuzlukları vardır. Geçiş yapmadan önce Turbopack uyumluluk listesine göre özel bağımlılıklarınızı kontrol edin.
Sonraki Adımlar
11 yerel ayarı, binlerce sayfayı ve kurumsal düzeyde performans gereksinimlerini karşılayan bir üretim Next.js 16 uygulaması oluşturmak önemli bir mühendislik girişimidir. ECOSIRE'ın ön uç ekibi tam olarak bunu sundu: çok dilli SEO, dinamik OG görselleri ve saniyeden kısa yanıt süreleri sunan 249 sayfalık Next.js 16 platformu.
Next.js uygulamanızı ölçeklendiriyorsanız veya kurumsal ön uç mühendislik desteğine ihtiyacınız varsa nasıl yardımcı olabileceğimizi görmek için geliştirme hizmetlerimizi keşfedin.
Yazan
ECOSIRE Research and Development Team
ECOSIRE'da kurumsal düzeyde dijital ürünler geliştiriyor. Odoo entegrasyonları, e-ticaret otomasyonu ve yapay zeka destekli iş çözümleri hakkında içgörüler paylaşıyor.
İlgili Makaleler
Internationalization in Next.js: 11-Locale Implementation
Build a production-ready 11-locale Next.js app with next-intl v4. Covers routing, RTL support, translation workflows, hreflang SEO, and server/client component patterns.
Nginx Production Configuration: SSL, Caching, and Security
Nginx production configuration guide: SSL termination, HTTP/2, caching headers, security headers, rate limiting, reverse proxy setup, and Cloudflare integration patterns.
Testing and Monitoring AI Agents in Production
A complete guide to testing and monitoring AI agents in production environments. Covers evaluation frameworks, observability, drift detection, and incident response for OpenClaw deployments.