9.5 נתיבים דינמיים ונתיבי API הרצאה
נתיבים דינמיים ונתיבי API - Dynamic Routes and API Routes¶
בשיעור זה נלמד ליצור נתיבים דינמיים שמשתנים לפי פרמטרים, ונכיר את Route Handlers שמאפשרים ליצור API endpoints ישירות בתוך פרויקט נקסט.
נתיבים דינמיים - Dynamic Segments¶
נתיבים דינמיים נוצרים על ידי שימוש בסוגריים מרובעים בשם התיקייה:
סגמנט בודד - [slug]¶
// app/blog/[slug]/page.tsx
interface PageProps {
params: { slug: string };
}
export default async function BlogPostPage({ params }: PageProps) {
const { slug } = params;
const res = await fetch(`https://api.example.com/posts/${slug}`);
const post = await res.json();
return (
<article className="max-w-3xl mx-auto p-6">
<h1 className="text-4xl font-bold mb-4">{post.title}</h1>
<time className="text-gray-500">
{new Date(post.date).toLocaleDateString("he-IL")}
</time>
<div className="mt-6 prose">{post.content}</div>
</article>
);
}
params.slugיכיל את הערך מה-URL/blog/my-post- params.slug = "my-post"/blog/hello- params.slug = "hello"
פרמטרים מרובים¶
// app/shop/[category]/[productId]/page.tsx
interface PageProps {
params: {
category: string;
productId: string;
};
}
export default async function ProductPage({ params }: PageProps) {
const { category, productId } = params;
return (
<div className="p-6">
<p className="text-gray-500">קטגוריה: {category}</p>
<h1 className="text-3xl font-bold">מוצר {productId}</h1>
</div>
);
}
נתיבים עם Catch-All¶
Catch-All - [...slug]¶
תופס את כל הסגמנטים שנותרו:
// app/docs/[...slug]/page.tsx
interface PageProps {
params: { slug: string[] };
}
export default function DocsPage({ params }: PageProps) {
const { slug } = params;
// /docs/getting-started → slug = ["getting-started"]
// /docs/api/auth/login → slug = ["api", "auth", "login"]
return (
<div className="p-6">
<h1 className="text-3xl font-bold">תיעוד</h1>
<p>נתיב: /{slug.join("/")}</p>
<p>עומק: {slug.length} רמות</p>
</div>
);
}
/docsלבד לא ייתפס - חייב לפחות סגמנט אחד
Optional Catch-All - [[...slug]]¶
כמו catch-all אבל תופס גם את הנתיב הבסיסי:
// app/docs/[[...slug]]/page.tsx
interface PageProps {
params: { slug?: string[] };
}
export default function DocsPage({ params }: PageProps) {
const { slug } = params;
if (!slug) {
return <h1>דף הבית של התיעוד</h1>;
}
return (
<div>
<h1>תיעוד: {slug.join(" / ")}</h1>
</div>
);
}
/docs- slug = undefined/docs/intro- slug = ["intro"]/docs/api/users- slug = ["api", "users"]
יצירת דפים סטטיים - generateStaticParams¶
כשרוצים לבנות דפים דינמיים כסטטיים בזמן ה-build:
// app/blog/[slug]/page.tsx
import { prisma } from "@/lib/prisma";
export async function generateStaticParams() {
const posts = await prisma.post.findMany({
select: { slug: true },
});
return posts.map((post) => ({
slug: post.slug,
}));
}
export default async function BlogPostPage({
params,
}: {
params: { slug: string };
}) {
const post = await prisma.post.findUnique({
where: { slug: params.slug },
});
if (!post) {
notFound();
}
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
generateStaticParamsמחזיר מערך של כל הפרמטרים האפשריים- נקסט בונה דף סטטי לכל אחד בזמן ה-build
- דפים חדשים שלא היו ב-build ייווצרו דינמית בבקשה הראשונה
שימוש עם פרמטרים מרובים¶
// app/shop/[category]/[productId]/page.tsx
export async function generateStaticParams() {
const products = await prisma.product.findMany({
include: { category: true },
});
return products.map((product) => ({
category: product.category.slug,
productId: product.id.toString(),
}));
}
נתיבי API - Route Handlers¶
Route Handlers מאפשרים ליצור API endpoints בתוך הפרויקט:
בקשות GET¶
// app/api/users/route.ts
import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
export async function GET() {
const users = await prisma.user.findMany();
return NextResponse.json(users);
}
בקשות POST¶
// app/api/users/route.ts
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
export async function POST(request: NextRequest) {
const body = await request.json();
const { name, email } = body;
if (!name || !email) {
return NextResponse.json(
{ error: "שם ואימייל הם שדות חובה" },
{ status: 400 }
);
}
const user = await prisma.user.create({
data: { name, email },
});
return NextResponse.json(user, { status: 201 });
}
בקשות PUT ו-DELETE¶
// app/api/posts/[id]/route.ts
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const post = await prisma.post.findUnique({
where: { id: parseInt(params.id) },
});
if (!post) {
return NextResponse.json({ error: "פוסט לא נמצא" }, { status: 404 });
}
return NextResponse.json(post);
}
export async function PUT(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const body = await request.json();
const post = await prisma.post.update({
where: { id: parseInt(params.id) },
data: body,
});
return NextResponse.json(post);
}
export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
) {
await prisma.post.delete({
where: { id: parseInt(params.id) },
});
return NextResponse.json({ message: "נמחק בהצלחה" });
}
עבודה עם Request ו-Response¶
קריאת Query Parameters¶
// app/api/search/route.ts
import { NextRequest, NextResponse } from "next/server";
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const query = searchParams.get("q");
const page = parseInt(searchParams.get("page") || "1");
const limit = parseInt(searchParams.get("limit") || "10");
// /api/search?q=nextjs&page=2&limit=20
const results = await prisma.post.findMany({
where: {
title: { contains: query || "" },
},
skip: (page - 1) * limit,
take: limit,
});
return NextResponse.json({
results,
page,
totalPages: Math.ceil(results.length / limit),
});
}
עבודה עם Headers¶
// app/api/protected/route.ts
import { NextRequest, NextResponse } from "next/server";
export async function GET(request: NextRequest) {
const authHeader = request.headers.get("authorization");
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return NextResponse.json(
{ error: "לא מורשה" },
{ status: 401 }
);
}
const token = authHeader.split(" ")[1];
// אימות הטוקן...
return NextResponse.json({ message: "תוכן מוגן" });
}
עבודה עם Cookies¶
// app/api/auth/route.ts
import { NextRequest, NextResponse } from "next/server";
import { cookies } from "next/headers";
export async function POST(request: NextRequest) {
const body = await request.json();
// התחברות...
const token = "jwt-token-here";
const response = NextResponse.json({ success: true });
response.cookies.set("session", token, {
httpOnly: true,
secure: true,
sameSite: "lax",
maxAge: 60 * 60 * 24 * 7, // שבוע
});
return response;
}
export async function GET() {
const cookieStore = cookies();
const session = cookieStore.get("session");
if (!session) {
return NextResponse.json({ error: "לא מחובר" }, { status: 401 });
}
return NextResponse.json({ user: "ישראל" });
}
מידלוור בסיסי - Middleware Basics¶
קובץ middleware.ts ברמה הראשית של הפרויקט:
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// הפניה מ-/old-page ל-/new-page
if (pathname === "/old-page") {
return NextResponse.redirect(new URL("/new-page", request.url));
}
// בדיקת אותנטיקציה לנתיבי דשבורד
if (pathname.startsWith("/dashboard")) {
const token = request.cookies.get("session");
if (!token) {
return NextResponse.redirect(new URL("/login", request.url));
}
}
return NextResponse.next();
}
// הגדרת נתיבים שהמידלוור רץ עליהם
export const config = {
matcher: ["/dashboard/:path*", "/old-page"],
};
- המידלוור רץ לפני כל בקשה (לפני Route Handlers ודפים)
- אפשר להגדיר matcher כדי שירוץ רק על נתיבים מסוימים
- שימושי להפניות, אותנטיקציה, הוספת headers
דוגמה מלאה - API לניהול מוצרים¶
// app/api/products/route.ts
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const category = searchParams.get("category");
const sort = searchParams.get("sort") || "createdAt";
const order = searchParams.get("order") || "desc";
const products = await prisma.product.findMany({
where: category ? { category } : undefined,
orderBy: { [sort]: order },
include: { reviews: { take: 3 } },
});
return NextResponse.json(products);
}
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { name, price, description, category } = body;
if (!name || !price) {
return NextResponse.json(
{ error: "שם ומחיר הם שדות חובה" },
{ status: 400 }
);
}
const product = await prisma.product.create({
data: { name, price, description, category },
});
return NextResponse.json(product, { status: 201 });
} catch (error) {
return NextResponse.json(
{ error: "שגיאה ביצירת המוצר" },
{ status: 500 }
);
}
}
// app/api/products/[id]/route.ts
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const product = await prisma.product.findUnique({
where: { id: parseInt(params.id) },
include: {
reviews: {
include: { user: { select: { name: true } } },
},
},
});
if (!product) {
return NextResponse.json({ error: "מוצר לא נמצא" }, { status: 404 });
}
return NextResponse.json(product);
}
export async function PUT(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const body = await request.json();
const product = await prisma.product.update({
where: { id: parseInt(params.id) },
data: body,
});
return NextResponse.json(product);
} catch (error) {
return NextResponse.json({ error: "מוצר לא נמצא" }, { status: 404 });
}
}
export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
await prisma.product.delete({
where: { id: parseInt(params.id) },
});
return NextResponse.json({ message: "המוצר נמחק" });
} catch (error) {
return NextResponse.json({ error: "מוצר לא נמצא" }, { status: 404 });
}
}
סיכום¶
- נתיבים דינמיים נוצרים עם
[slug]בשם התיקייה [...slug]תופס כל הסגמנטים,[[...slug]]תופס גם את הנתיב הריקgenerateStaticParamsמאפשר בנייה סטטית של דפים דינמיים- Route Handlers (
route.ts) מגדירים API endpoints עם GET, POST, PUT, DELETE - עובדים עם NextRequest ו-NextResponse לקריאת query params, headers ו-cookies
- Middleware רץ לפני כל בקשה ומתאים להפניות ואותנטיקציה