React Router v7 Framework Modu: Remix ile Birleşme ve Full-Stack Geçiş Rehberi
React ekosisteminde 2024'ün en önemli gelişmelerinden biri sessiz sedasız gerçekleşti: Remix, React Router ile birleşti. React Router v7 artık sadece client-side bir yönlendirme kütüphanesi değil; server-side rendering, data loading, form handling ve daha fazlasını sunan tam kapsamlı bir full-stack framework. Bu yazıda bu birleşmenin ne anlama geldiğini, nasıl çalıştığını ve projelerinizde nasıl kullanabileceğinizi adım adım inceleyeceğiz.
Neden Birleştiler? Arka Plan
Remix ekibi, 2022'den bu yana React Router ile Remix arasındaki çizgiyi aşamalı olarak bulanıklaştırıyordu. Remix v2 zaten React Router v6'nın üzerine inşa edilmişti ve iki proje arasındaki fark giderek azalıyordu. Mantıksal sonuç belliydi: İki ayrı proje yerine tek bir çatı altında birleşmek.
Bu birleşmenin temel motivasyonları:
- Kod tekrarını ortadan kaldırmak: Remix ve React Router'ın paylaştığı devasa ortak kod tabanı tek noktada yönetilecek.
- Geçiş sürecini kolaylaştırmak: Kullanıcılar React Router'dan Remix'e geçmek için tamamen farklı bir kütüphane öğrenmek zorunda kalmayacak.
- Kademeli benimseme (incremental adoption): Projeler ihtiyaçlarına göre sadece client-side routing veya tam framework modunu seçebilecek.
- Daha güçlü bir topluluk: İki ayrı ekosistem yerine tek bir güçlü topluluk.
Ryan Florence'ın ifadesiyle: "Remix, React Router'ın bir sonraki büyük versiyonudur."
İki Çalışma Modu: Library vs Framework
React Router v7'nin en önemli özelliği iki farklı modda çalışabilmesidir:
1. Library Modu (Klasik Kullanım)
Daha önceki React Router sürümlerinde alışık olduğunuz kullanım. Sadece client-side routing yaparsınız, herhangi bir sunucu tarafı işlevsellik yoktur. Mevcut SPA projelerinizde doğrudan kullanabilirsiniz.
// Library modu - klasik kullanım
import { BrowserRouter, Routes, Route } from "react-router";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/products/:id" element={<Product />} />
</Routes>
</BrowserRouter>
);
}2. Framework Modu (Remix Gücü)
İşte asıl heyecan verici kısım burası. Framework modunu etkinleştirdiğinizde React Router, Remix'in sunduğu tüm yetenekleri devralır:
- Dosya tabanlı yönlendirme (file-based routing)
- Server-side rendering (SSR)
- Sunucu tarafı veri yükleme (
loader) - Sunucu tarafı form işleme (
action) - Streaming SSR
- Otomatik code splitting
- Type-safe routing
Framework Moduna Başlarken
Framework modunu kullanmak için projenizi Vite üzerinden yapılandırmanız gerekir. React Router v7, build aracı olarak Vite kullanır.
Kurulum
npx create-react-router@latest my-app
cd my-app
npm install
npm run devVite Konfigürasyonu
// vite.config.ts
import { reactRouter } from "@react-router/dev/vite";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
plugins: [
reactRouter(), // Framework modunu etkinleştirir
tsconfigPaths(),
],
});React Router Konfigürasyonu
// react-router.config.ts
import type { Config } from "@react-router/dev/config";
export default {
// SSR'ı etkinleştir
ssr: true,
// Uygulamanın dizin yapısı
appDirectory: "app",
} satisfies Config;Dosya Tabanlı Yönlendirme (File-Based Routing)
Framework modunda route'larınızı routes.ts dosyasında tanımlarsınız. Bu, Remix'in dosya tabanlı yönlendirme sisteminin evrimleşmiş halidir:
// app/routes.ts
import {
type RouteConfig,
index,
route,
layout,
prefix,
} from "@react-router/dev/routes";
export default [
// Ana sayfa
index("routes/home.tsx"),
// Basit route
route("about", "routes/about.tsx"),
// Layout ile sarmalanmış route'lar
layout("routes/dashboard/layout.tsx", [
index("routes/dashboard/index.tsx"),
route("settings", "routes/dashboard/settings.tsx"),
route("profile", "routes/dashboard/profile.tsx"),
]),
// Prefix ile gruplandırma
...prefix("api", [
route("users", "routes/api/users.tsx"),
route("products", "routes/api/products.tsx"),
]),
] satisfies RouteConfig;Bu yapı, Remix'in v2_routeConvention'ından daha esnek ve açıktır. Route'larınızı istediğiniz klasör yapısında organize edebilir, routes.ts içinde sadece ilişkilendirmeyi yaparsınız.
Loader ve Action: Sunucu Tarafı Veri Yönetimi
Framework modunun en güçlü özelliği, her route için sunucu tarafında çalışan loader ve action fonksiyonlarıdır.
Loader ile Veri Yükleme
// app/routes/products.tsx
import type { Route } from "./+types/products";
// Bu fonksiyon SUNUCUDA çalışır
export async function loader({ request }: Route.LoaderArgs) {
const url = new URL(request.url);
const search = url.searchParams.get("q") || "";
const products = await db.product.findMany({
where: {
name: { contains: search, mode: "insensitive" },
},
orderBy: { createdAt: "desc" },
});
return { products, search };
}
// Component otomatik olarak loader verisini alır
export default function Products({ loaderData }: Route.ComponentProps) {
const { products, search } = loaderData;
return (
<div>
<h1>Ürünler</h1>
<SearchBar defaultValue={search} />
<ul>
{products.map((product) => (
<li key={product.id}>
<a href={`/products/${product.id}`}>{product.name}</a>
<span>{product.price} TL</span>
</li>
))}
</ul>
</div>
);
}Action ile Form İşleme
// app/routes/products.new.tsx
import { redirect } from "react-router";
import type { Route } from "./+types/products.new";
// Form submit edildiğinde SUNUCUDA çalışır
export async function action({ request }: Route.ActionArgs) {
const formData = await request.formData();
const name = formData.get("name") as string;
const price = parseFloat(formData.get("price") as string);
// Validasyon
const errors: Record<string, string> = {};
if (!name || name.length < 2) {
errors.name = "Ürün adı en az 2 karakter olmalıdır.";
}
if (isNaN(price) || price <= 0) {
errors.price = "Geçerli bir fiyat giriniz.";
}
if (Object.keys(errors).length > 0) {
return { errors };
}
const product = await db.product.create({
data: { name, price },
});
return redirect(`/products/${product.id}`);
}
export default function NewProduct({ actionData }: Route.ComponentProps) {
const errors = actionData?.errors;
return (
<div>
<h1>Yeni Ürün Ekle</h1>
<form method="post">
<div>
<label htmlFor="name">Ürün Adı</label>
<input type="text" id="name" name="name" required />
{errors?.name && <p className="error">{errors.name}</p>}
</div>
<div>
<label htmlFor="price">Fiyat (TL)</label>
<input type="number" id="price" name="price" step="0.01" required />
{errors?.price && <p className="error">{errors.price}</p>}
</div>
<button type="submit">Kaydet</button>
</form>
</div>
);
}Dikkat edin: useLoaderData() ve useActionData() hook'ları yerine artık props üzerinden type-safe veri alıyorsunuz. Bu, TypeScript deneyimini büyük ölçüde iyileştiriyor.
Type-Safe Routing: Otomatik Tip Üretimi
React Router v7'nin en çarpıcı yeniliklerinden biri otomatik tip üretimidir. Her route dosyası için ./+types/ dizininde tipler otomatik oluşturulur:
// app/routes/products.$id.tsx
import type { Route } from "./+types/products.$id";
export async function loader({ params }: Route.LoaderArgs) {
// params.id otomatik olarak string tipinde!
const product = await db.product.findUnique({
where: { id: params.id },
});
if (!product) {
throw new Response("Bulunamadı", { status: 404 });
}
return { product };
}
export default function ProductDetail({ loaderData }: Route.ComponentProps) {
// loaderData.product tipi otomatik olarak çıkarılır
const { product } = loaderData;
return (
<div>
<h1>{product.name}</h1>
<p>{product.price} TL</p>
</div>
);
}Artık useParams() ile as string yazmak veya useLoaderData<typeof loader>() gibi manuel tip tanımlamaları yapmak zorunda değilsiniz.
Pending UI ve Optimistic Updates
React Router v7 framework modu, navigasyon sırasında kullanıcıya geri bildirim vermek için güçlü araçlar sunar:
import { useNavigation, Form } from "react-router";
export default function ProductForm() {
const navigation = useNavigation();
const isSubmitting = navigation.state === "submitting";
return (
<Form method="post">
<input type="text" name="name" disabled={isSubmitting} />
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? "Kaydediliyor..." : "Kaydet"}
</button>
</Form>
);
}Mevcut Projeden Geçiş Stratejileri
Remix v2'den React Router v7'ye
Bu geçiş nispeten basittir çünkü API'ler büyük ölçüde aynıdır:
- Paketleri değiştirin:
npm uninstall @remix-run/react @remix-run/node @remix-run/serve
npm install react-router @react-router/node @react-router/serve- Import'ları güncelleyin:
// Eski (Remix v2)
import { useLoaderData, json } from "@remix-run/react";
// Yeni (React Router v7)
import { useLoaderData } from "react-router";
// json() artık gerekli değil, doğrudan obje dönebilirsinizjson()wrapper'ını kaldırın:
// Eski
export async function loader() {
return json({ message: "hello" });
}
// Yeni - doğrudan return
export async function loader() {
return { message: "hello" };
}Route config dosyasını oluşturun (
routes.ts)vite.config.ts'i güncelleyin (@react-router/dev/vitekullanın)
React Router v6 SPA'dan Framework Moduna
Bu geçiş daha kapsamlıdır ve kademeli yaklaşım önerilir:
- Önce v7 library moduna güncelleyin.
- Vite plugin'ini ekleyin.
- Route'larınızı tek tek framework moduna taşıyın.
useEffect+fetchkalıplarınıloader'lara dönüştürün.
// ÖNCE: Client-side veri çekme
function Products() {
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch("/api/products")
.then((res) => res.json())
.then(setProducts)
.finally(() => setLoading(false));
}, []);
if (loading) return <Spinner />;
return <ProductList products={products} />;
}
// SONRA: Server-side loader
export async function loader() {
const products = await db.product.findMany();
return { products };
}
export default function Products({ loaderData }: Route.ComponentProps) {
return <ProductList products={loaderData.products} />;
}SSR vs SPA vs Static: Esnek Dağıtım
React Router v7 framework modu, farklı dağıtım stratejilerini destekler:
// react-router.config.ts
import type { Config } from "@react-router/dev/config";
export default {
// Tam SSR (varsayılan)
ssr: true,
// Veya SPA modu (SSR kapalı)
// ssr: false,
// Veya belirli route'ları pre-render et (SSG)
async prerender() {
return ["/", "/about", "/pricing"];
},
} satisfies Config;Bu esneklik sayesinde aynı proje içinde bazı sayfalar SSR ile, bazıları statik olarak oluşturulabilir.
Deployment Seçenekleri
React Router v7 framework modu çeşitli platformlarda çalışır:
- Node.js (
@react-router/node) - Cloudflare Workers/Pages (
@react-router/cloudflare) - Vercel (Edge ve Node.js)
- Netlify
- Deno
- AWS Lambda (adapter ile)
// server.ts - Node.js adapter örneği
import { createRequestHandler } from "@react-router/node";
import express from "express";
const app = express();
app.use(
express.static("build/client", {
maxAge: "1y",
immutable: true,
})
);
app.all("*", createRequestHandler({ build: () => import("./build/server") }));
app.listen(3000, () => {
console.log("Server 3000 portunda çalışıyor");
});Performans Avantajları
Framework modunun SPA yaklaşımına göre somut avantajları:
| Özellik | SPA (Library Modu) | Framework Modu |
|---|---|---|
| İlk Yükleme | Yavaş (waterfall) | Hızlı (SSR + streaming) |
| SEO | Zayıf | Güçlü |
| Code Splitting | Manuel | Otomatik |
| Data Fetching | Client waterfall | Paralel server-side |
| Tip Güvenliği | Manuel | Otomatik |
| Progressive Enhancement | Yok | Var |
Sonuç
React Router v7'nin framework modu, React ekosisteminde önemli bir dönüm noktasıdır. Remix'in yıllarca geliştirdiği full-stack yetenekler artık dünyanın en popüler React routing kütüphanesinin bir parçası oldu.
Özetle:
- Remix kullanıcıları için bu bir "yeniden öğrenme" değil, doğal bir evrimdir. Import'ları değiştirin, devam edin.
- React Router v6 kullanıcıları için kademeli bir geçiş yolu mevcuttur. Library modunda kalmaya devam edebilir veya framework moduna geçebilirsiniz.
- Yeni projelere başlayanlar için React Router v7 framework modu, Next.js'e güçlü bir alternatif olarak konumlanmaktadır.
- Type-safe routing, otomatik code splitting ve sunucu tarafı veri yönetimi gibi özellikler geliştirici deneyimini ciddi ölçüde iyileştirmektedir.
Bu birleşme, "hangi router'ı kullanmalıyım?" sorusunu ortadan kaldırıyor. React Router v7 artık hem basit SPA'lar hem de karmaşık full-stack uygulamalar için tek bir çözüm sunuyor. Eğer hâlâ useEffect + fetch ile veri çekiyorsanız, framework moduna geçerek hem performansınızı hem de kod kalitenizi bir üst seviyeye taşımanın tam zamanı.