Next.js 16 App Router — מאפס לפריסה ב-Vercel
מדריך מעשי ל-Next.js 16 App Router — Server Components, Server Actions, Cache Components, וקובץ proxy.ts החדש — מהפרויקט הראשון ועד פריסה ב-Vercel.
למי זה מתאים: אתה כבר יודע React בסיסי —useState,useEffect, קומפוננטים. בנית פעם או פעמיים אפליקציה ב-Vite או ב-Create React App. עכשיו אתה רוצה להבין מה כל הרעש סביב Next.js 16 App Router, איך הוא באמת עובד, ואיך מעלים פרויקט אמיתי ל-Vercel בלי להעתיק קוד מסרטון YouTube. אין הנחה של ידע ב-SSR, RSC או Cache Components.
זה מדריך מעשי ל-Next.js 16 App Router: נתחיל מ״למה בכלל לעבור״, נעבור על המודל המנטלי של Server Components מול Client Components, על Server Actions ועל מודל הקאשינג החדש (Cache Components), ונסיים בפריסה מלאה ל-Vercel. אם אתה עדיין מתלבט אם בכלל צריך Next.js — הדיון על SPA מול server מופיע גם במדריך React SPA ו-SEO, ושיקולי האחסון מפורטים בVercel מול Firebase Hosting.
למי המדריך הזה (ולמי לא)
אם אתה בונה דף נחיתה סטטי, פורטפוליו, או אתר תדמיתי בלי backend — אל תעבור ל-Next.js. Vite + React + Tailwind יספיקו לך, ירוצו מהר יותר ב-dev, ויעלו לך אפס שקל ב-Firebase Hosting או ב-Cloudflare Pages. גם המדריך הזה לא בשבילך.
אם אתה בונה משהו עם server, מסד נתונים, טפסים, אימות משתמש, פיד דינמי, אזור משתמשים, או SaaS — אז Next.js שווה את הלימוד. ה-App Router נותן לך Server Components, Server Actions, ו-streaming מובנים. אתה לא תצטרך API נפרד בשביל כל פעולה קטנה.
המדריך הזה מניח:
- אתה מכיר React ברמה של ״יודע לבנות טופס עם useState״
- יש לך Node.js 20.9+ מותקן (Next.js 16 לא תומך ב-Node 18)
- יש לך חשבון GitHub
- יש לך 40 דקות פנויות
המדריך לא מכסה: Server-side authentication מלא, Stripe, מסדי נתונים. נדבר על זה במדריכים נפרדים.
מה השתנה ב-Next.js 16
Next.js 16 יצא באוקטובר 2025 עם כמה שינויים גדולים. אם קראת מדריכים ישנים יותר — שים לב, הרבה דברים השתנו.
Turbopack הוא הברירת מחדל. הבאנדלר החדש של Vercel יציב לפיתוח ולבנייה לפרודקשן. אתה לא צריך לכתוב --turbopack בסקריפטים. זה רץ פי 2-5 מהר יותר מ-Webpack בבנייה, ופי 10 מהר יותר ב-Fast Refresh. אם יש לך webpack.config מותאם, תצטרך להעביר אותו או להריץ next build --webpack ידנית. ראה Turbopack stable.
Cache Components — מודל קאשינג חדש. במקום קאשינג אימפליציטי שאף אחד לא הבין מתי קורה, ב-Next 16 הכל דינמי כברירת מחדל ואתה מסמן ידנית מה לקאש עם הדירקטיבה "use cache". הדגל הישן experimental.ppr נמחק; הוא הוחלף ב-cacheComponents: true ב-next.config.ts. מאז גרסה 16.2 כל ה-API של Cache Components (use cache, cacheLife, cacheTag, updateTag) יציב — הקידומת unstable_ ירדה, אז אם ראית בקוד ישן unstable_cacheLife כדאי לעדכן. ראה Cache Components docs.
middleware.ts נקרא עכשיו proxy.ts. האריזה הישנה לא נמחקה, אבל היא deprecated. הקובץ החדש רץ רק על Node.js runtime — לא יותר Edge. הסיבה: Vercel רוצים שתשתמש ב-proxy.ts רק לדברים רשתיים קלים (redirect, rewrite, header tweak), ולא לאימות כבד או DB lookups. ראה middleware to proxy.
React 19.2 ו-React Compiler 1.0. הקומפיילר עכשיו יציב — מפעילים אותו עם reactCompiler: true ב-config. הוא ממחזר קומפוננטים אוטומטית (memoization), כך שאתה כותב פחות useMemo/useCallback ידני. החיסרון: הוא רץ על Babel, אז הבילד יהיה איטי יותר. Next.js 16 ממתן את זה עם שכבת SWC שמסננת מראש ומריצה את הקומפיילר רק על הקבצים הרלוונטיים, אבל זה עדיין לא דלוק כברירת מחדל — הפעל ידנית כשאתה רוצה את ה-memoization האוטומטי.
APIs אסינכרוניים — חובה. cookies(), headers(), draftMode(), params, ו-searchParams הם עכשיו Promises. צריך await. ב-Next 15 זה היה אופציונלי, ב-16 זה שובר אם לא תעדכן.
Node.js 20.9+ מינימום. Node 18 לא נתמך יותר. TypeScript 5.1+.
next lint ירד מהרכבת. רץ Biome או ESLint ישירות. next build כבר לא מריץ linting.
Next.js vs Vite SPA — מתי כל אחד
| מה | Vite + React SPA | Next.js 16 App Router |
|---|---|---|
| תרחיש טיפוסי | פורטפוליו, dashboard פנימי, MVP מהיר | אתר תדמיתי עם CMS, SaaS, חנות, בלוג |
| SEO | חלש (CSR בלבד) | חזק (SSR + streaming) |
| Backend | אין — צריך פרויקט נפרד | מובנה (Server Components, Route Handlers) |
| Initial load | bundle של JS מלא | HTML מוכן + hydration |
| עקומת לימוד | שטוחה | תלולה — RSC, caching, boundaries |
| Dev server | מהיר מאוד | מהיר (Turbopack) — אבל יש compile step |
| Hosting | סטטי, חינמי כמעט בכל מקום | Vercel, Netlify, או Node.js עם תאימות |
| המלצה | כשאין backend או שיש backend נפרד | כשרוצים full-stack בלי לנהל שני פרויקטים |
הכלל הפשוט: אם אתה רואה את עצמך כותב fetch('/api/...') הרבה — Next.js יחסוך לך כאב ראש.
App Router בקצרה — folders == routes
ה-App Router הוא הניתוב החדש של Next.js. הרעיון: שמות תיקיות הופכים ל-URLs. שמות קבצים מיוחדים בתוך כל תיקייה קובעים מה רץ באותו route.
app/
├── layout.tsx → wrapper של כל הדפים
├── page.tsx → המסך הראשי /
├── globals.css → CSS גלובלי
├── about/
│ └── page.tsx → /about
├── blog/
│ ├── page.tsx → /blog (רשימת פוסטים)
│ └── [slug]/
│ └── page.tsx → /blog/my-post (פוסט בודד)
└── dashboard/
├── layout.tsx → wrapper רק ל-/dashboard
├── page.tsx → /dashboard
├── loading.tsx → spinner אוטומטי בטעינה
├── error.tsx → error boundary
└── not-found.tsx → 404 מותאם
הקבצים המיוחדים (כולם בתוך תיקיית route):
page.tsx— התוכן של ה-URL הזה. חובה אחד כזה כדי שה-route יהיה נגיש.layout.tsx— wrapper משותף לכל הילדים. נשמר ב-mount בין navigations (לא נטען מחדש).loading.tsx— מוצג אוטומטית בזמן שה-page טוען. Suspense מובנה.error.tsx— נתפס שגיאות runtime באותו subtree. חייב להיות Client Component.not-found.tsx— מוצג כשקראת ל-notFound()מתוך הקוד.proxy.ts— בשורש הפרויקט (לא בתוךapp/). רץ לפני כל בקשה.
Dynamic segments מסומנים בסוגריים מרובעים: [slug] הופך לפרמטר. תיקייה [...slug] מקבלת מספר חלקים (catch-all).
// app/blog/[slug]/page.tsx
export default async function BlogPost({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params // params הוא Promise ב-Next 16
return <h1>{slug}</h1>
}
שים לב — params עכשיו Promise. צריך await. זה שינוי שובר מ-Next 15.
proxy.ts — מה שהיה פעם middleware
ב-Next 16 הקובץ שרץ לפני כל בקשה נקרא proxy.ts ויושב בשורש הפרויקט (ליד app/, לא בתוכו). הוא החליף את middleware.ts, והשינוי הגדול: הוא רץ רק על Node.js runtime, לא Edge. בפועל זה דווקא משחרר — עכשיו יש לך גישה לכל ה-API של Node בתוך ה-interceptor: אימות JWT עם crypto המובנה, בדיקת session מול DB, מה שתרצה.
// proxy.ts — בשורש הפרויקט
import { NextResponse, type NextRequest } from 'next/server'
export function proxy(req: NextRequest) {
// הגנה על אזור משתמש — redirect אם אין token
const token = req.cookies.get('session')?.value
if (req.nextUrl.pathname.startsWith('/dashboard') && !token) {
return NextResponse.redirect(new URL('/login', req.url))
}
return NextResponse.next()
}
export const config = {
matcher: ['/dashboard/:path*'],
}
הכלל של Vercel: שמור את proxy.ts לדברים רשתיים קלים — redirect, rewrite, header tweak, בדיקת token זריזה. את האימות הכבד (lookup מלא של המשתמש, הרשאות) עשה בתוך ה-Server Component או ה-Server Action עצמו, לא ב-proxy שרץ על כל בקשה. אם יש לך עדיין middleware.ts ישן — הרץ את ה-codemod npx @next/codemod@canary upgrade latest והוא ישנה את השם ואת חתימת הפונקציה בשבילך.
Server Components vs Client Components
זה החלק שמבלבל הכי הרבה אנשים שבאים מ-CRA או Vite. תקשיב טוב.
ברירת המחדל ב-App Router היא Server Component. כל קובץ ב-app/ שלא מסומן במפורש — רץ על השרת. הוא לא נשלח כ-JS לדפדפן. הוא מורנדר כ-HTML ונדחף ללקוח.
Client Component הוא קובץ שמתחיל בשורה הראשונה (לפני ה-imports) ב-"use client". אז ורק אז הקוד עובר ל-bundle של הדפדפן ומקבל hydrate.
מתי Server Component?
- מציג נתונים שצריך לטעון משרת (DB, API, FS)
- הכל סטטי — heading, paragraph, image
- אין
useState, איןonClick, איןuseEffect
// app/blog/page.tsx — Server Component (ברירת מחדל)
import { db } from '@/lib/db'
export default async function BlogPage() {
const posts = await db.posts.findMany() // רץ על השרת
return (
<ul>
{posts.map(p => <li key={p.id}>{p.title}</li>)}
</ul>
)
}
שם לב — הקומפוננט הוא async. רק Server Components יכולים להיות async.
מתי Client Component?
- צריך state (
useState) - צריך אירוע (
onClick,onChange) - צריך browser API (
window,localStorage) - צריך React hooks כמו
useEffect
// app/components/counter.tsx
'use client'
import { useState } from 'react'
export default function Counter() {
const [n, setN] = useState(0)
return <button onClick={() => setN(n + 1)}>{n}</button>
}
הכלל החשוב שמפספסים
"use client" הוא גבול, לא תווית על קומפוננט בודד. כל קומפוננט שמיובא לתוך קובץ עם "use client" הופך אוטומטית גם הוא ל-Client. הקומפיילר לא בודק — הוא רק עוקב אחרי הגבול.
הדרך הנכונה לערבב: בנה את העץ כ-Server, ושים Client Components כעלים. אם אתה חייב Server בתוך Client — תעביר אותו כ-children prop, לא תייבא אותו.
// ❌ לא טוב — ServerStuff נכנס ל-Client bundle
'use client'
import ServerStuff from './server-stuff'
export default function ClientWrap() {
return <div><ServerStuff /></div>
}
// ✅ טוב — ServerStuff נשאר server, מועבר כ-children
// app/page.tsx (Server)
import ClientWrap from './client-wrap'
import ServerStuff from './server-stuff'
export default function Page() {
return <ClientWrap><ServerStuff /></ClientWrap>
}
זה הדפוס שתשתמש בו הכי הרבה. לזכור: server יכול להכיל client, client יכול להציג server רק דרך children.
Data fetching — איך מביאים נתונים
ב-App Router, Server Component יכול להיות async. אתה פשוט קורא ל-await fetch(...) בתוך הקומפוננט, וזה רץ על השרת לפני שהדף נשלח.
// app/news/page.tsx
export default async function News() {
const res = await fetch('https://api.example.com/news')
const data = await res.json()
return (
<ul>{data.map(n => <li key={n.id}>{n.title}</li>)}</ul>
)
}
זה רץ בכל בקשה — דינמי. אם זה כבד ולא משתנה הרבה, אתה רוצה לקאש.
"use cache" directive
ב-Next 16 הקאשינג הוא opt-in. אתה מסמן את הפונקציה או הקומפוננט במחרוזת "use cache" בשורה הראשונה, וה-Next.js יוצר מפתח קאש אוטומטית על בסיס הארגומנטים.
import { cacheLife, cacheTag } from 'next/cache'
async function getPosts() {
'use cache'
cacheLife('hours') // לכמה זמן הקאש תקף
cacheTag('posts') // תווית לפי שם — שימושי ל-invalidation
const res = await fetch('https://api.example.com/posts')
return res.json()
}
export default async function Page() {
const posts = await getPosts()
return <PostList posts={posts} />
}
הפעלה ראשונה: צריך לוודא ש-cacheComponents: true ב-next.config.ts:
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
}
export default nextConfig
cacheLife מקבל profile מובנה: 'seconds', 'minutes', 'hours', 'days', 'weeks', 'max'. אפשר גם להגדיר profiles משלך ב-config. ראה cacheLife reference.
cacheTag מתייג את הקאש. אחר כך אפשר לבטל אותו לפי תווית — מתי, נראה בסעיף Server Actions.
האסטרטגיה הפשוטה
- דינמי כברירת מחדל. אל תקאש סתם — Next.js יקרא ל-DB בכל בקשה. זה בסדר לרוב הדפים.
- Cache למידע שמשותף בין משתמשים — בלוג, רשימת מוצרים, מטא-דאטה.
"use cache: private"למידע פר-משתמש שמשתמש ב-cookies או headers (variant של הדירקטיבה).- PPR מובנה ב-Cache Components: דברים סטטיים מורנדרים ב-build, דברים דינמיים נטענים כ-Suspense.
הנה איך זה נראה בפועל. דף מוצר אופייני מערבב שלושה סוגי תוכן: כותרת ותיאור שזהים לכולם (קאש ציבורי), מחיר שמשתנה לעיתים רחוקות (קאש קצר), ועגלת הקניות של המשתמש (דינמי, לעולם לא בקאש). ב-Cache Components כל אחד יושב ב״שכבה״ משלו:
// app/product/[id]/page.tsx
import { Suspense } from 'react'
import { cacheLife } from 'next/cache'
async function ProductInfo({ id }: { id: string }) {
'use cache' // משותף לכל המשתמשים — נכנס ל-build/CDN
cacheLife('days')
const p = await db.products.find(id)
return <header><h1>{p.name}</h1><p>{p.desc}</p></header>
}
async function Cart() { // דינמי — קורא cookies, אף פעם לא בקאש
const items = await getCartFromSession()
return <aside>{items.length} פריטים</aside>
}
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
return (
<>
<ProductInfo id={id} />
<Suspense fallback={<p>טוען עגלה…</p>}>
<Cart />
</Suspense>
</>
)
}
זה בדיוק PPR בפעולה: ProductInfo מגיע מיידית כ-HTML סטטי, ו-Cart נשלח ב-streaming כשהוא מוכן. המשתמש רואה את רוב הדף מיד, בלי לחכות ל-session lookup.
טיפ לביצועים: הקאשינג הזה הוא רק חצי מהסיפור. כדי באמת לעבור את הסף של Core Web Vitals שווה לקרוא את המדריך Core Web Vitals 2026 — LCP טוב מגיע מ-streaming של ה-shell הסטטי, בדיוק מה ש-PPR נותן לך כאן בחינם.
Server Actions — טפסים בלי API routes
זה הכלי החזק ביותר ב-Next.js, ובדרך כלל מה ששווה את כל הסיבוב.
Server Action הוא פונקציה שמתחילה ב-"use server". אתה יכול לקרוא לה ישירות מטופס או מ-Client Component, וה-Next.js יטפל ב-serialization, ה-network call, וה-revalidation.
// app/contact/actions.ts
'use server'
import { revalidatePath } from 'next/cache'
export async function submitContact(formData: FormData) {
const name = formData.get('name') as string
const email = formData.get('email') as string
const message = formData.get('message') as string
// validate
if (!email || !email.includes('@')) {
return { error: 'אימייל לא תקין' }
}
// save to DB / send email
await db.contacts.create({ data: { name, email, message } })
// refresh the page so the new data shows
revalidatePath('/contact')
return { success: true }
}
ואז בטופס — שים לב, אין fetch, אין axios, אין /api/contact route:
// app/contact/page.tsx
import { submitContact } from './actions'
export default function ContactPage() {
return (
<form action={submitContact}>
<input name="name" placeholder="שם" required />
<input name="email" type="email" placeholder="מייל" required />
<textarea name="message" required />
<button type="submit">שלח</button>
</form>
)
}
זה עובד גם בלי JavaScript — מכיוון שזה submit רגיל של HTML form, הוא ירוץ גם אם המשתמש כיבה JS. עם JS פעיל — Next.js יתפוס את ה-submit, יקרא ל-action, ויעדכן את הדף בלי full reload.
תגיות קאש
אם השתמשת ב-cacheTag('posts') בקומפוננט שמציג פוסטים, אתה יכול לבטל אותו אחרי השמירה:
'use server'
import { updateTag } from 'next/cache'
export async function createPost(data: FormData) {
await db.posts.create({ /* ... */ })
updateTag(`posts`) // ה-cache מתעדכן מיידית
}
updateTag (חדש ב-Next 16) נותן read-your-writes — המשתמש שביצע את הפעולה רואה את השינוי שלו מיד. שים לב למגבלה: updateTag עובד רק בתוך Server Action, לא ב-Route Handler ולא ב-webhook. לעומתו revalidateTag('posts', 'max') נותן stale-while-revalidate (הקאש הישן מוצג ועדכון רץ ברקע), והוא מה שתשתמש בו ב-webhook או ב-cron job שמגיע מבחוץ. ראה updateTag docs.
שים לב: ב-Next 16,revalidateTag('posts')בלי הארגומנט השני זה deprecated. צריךrevalidateTag('posts', 'max')אוupdateTag('posts').
שלב-שלב: יצירת הפרויקט הראשון
מספיק תיאוריה. בואו נבנה.
1. וודא שיש לך Node.js 20.9+
node --version
צריך לראות v20.9.x או יותר. אם לא — לך ל-nodejs.org ותתקין LTS.
2. הרץ create-next-app
npx create-next-app@latest
ה-CLI ישאל אותך סדרת שאלות. התשובות שאני ממליץ:
- What is your project named? — שם הפרויקט, למשל
my-app - Would you like to use TypeScript? —
Yes - Which linter would you like to use? —
ESLint(או דלג, Biome חזק יותר) - Would you like to use React Compiler? —
No(בילדים איטיים יותר, תפעיל בהמשך אם רוצים) - Would you like to use Tailwind CSS? —
Yes - Would you like your code in a
src/directory? —No(פשוט יותר בהתחלה) - Would you like to use App Router? —
Yes(זה הנושא של המדריך הזה) - Would you like to customize the import alias? —
No(ברירת מחדל@/*מעולה)
ה-CLI יוריד dependencies, ייצור את הפרויקט, וזהו. Turbopack כבר מוגדר כברירת מחדל.
3. רוץ ב-dev
cd my-app
npm run dev
תפתח את http://localhost:3000. אתה אמור לראות את עמוד ברירת המחדל של Next.js.
ב-Terminal תראה משהו כמו:
▲ Next.js 16 (Turbopack)
- Local: http://localhost:3000
- Environments: .env.local
✓ Starting...
✓ Ready in 615ms
4. ערוך את עמוד הבית
תפתח את app/page.tsx ב-VS Code. תמחק את הכל, ותדביק:
export default function Home() {
return (
<main className="min-h-screen flex items-center justify-center">
<h1 className="text-4xl font-bold">שלום עולם</h1>
</main>
)
}
תשמור. הדפדפן יתעדכן אוטומטית (Fast Refresh). זה אמור להיות כמעט מיידי — Turbopack.
5. תוסיף route חדש
צור תיקייה app/about/ ובתוכה קובץ page.tsx:
// app/about/page.tsx
export default function About() {
return <h1>על המוצר</h1>
}
גש ל-http://localhost:3000/about. הנה זה.
6. בנה לפרודקשן
npm run build
תראה משהו כמו:
▲ Next.js 16 (Turbopack)
✓ Compiled successfully in 8.4s
✓ Finished TypeScript in 1.1s
✓ Collecting page data in 208ms
✓ Generating static pages (5/5)
✓ Finalizing page optimization
לבדיקה מקומית של ה-build:
npm run start
זה רץ על port 3000 במצב פרודקשן.
פריסה ב-Vercel
Next.js שייכת ל-Vercel, אז הפריסה שם היא הקלה ביותר — בלי הגדרות, בלי קבצי config, רק לחיצה.
1. דחוף את הקוד ל-GitHub
ב-Terminal, בתוך תיקיית הפרויקט:
git init
git add .
git commit -m "initial"
תיצור ריפו ב-github.com, העתק את ה-URL, ואז:
git remote add origin https://github.com/your-username/my-app.git
git branch -M main
git push -u origin main
2. תחבר את הריפו ל-Vercel
נכנס ל-vercel.com, תתחבר עם GitHub. בלוח הבקרה תלחץ Add New → Project. בחר את הריפו שיצרת. Vercel יזהה אוטומטית שזה Next.js — לא צריך לשנות שום הגדרה. תלחץ Deploy.
תוך כדקה-שתיים יהיה לך URL כמו my-app-noam.vercel.app שעובד.
3. משתני סביבה
אם הפרויקט שלך משתמש ב-API keys (Stripe, מודל AI, DB), אל תדחוף אותם ל-Git. הוסף אותם ב-Vercel. אם אתה מתכוון לקרוא למודלים של AI מתוך Server Actions, שווה לנתב אותם דרך שכבה אחת במקום מפתח לכל ספק — הדפוס הזה מוסבר בVercel AI Gateway למודלים מרובים:
Settings → Environment Variables
הוסף key/value. תבחר באילו סביבות הוא תקף: Production, Preview, Development. אחרי הוספה — תעשה redeploy או דחוף commit חדש.
שים לב: משתנה שמתחיל ב-NEXT_PUBLIC_ יהיה נגיש גם בקוד הלקוח (bundle). משתנה רגיל — רק בשרת. אם שכחת את ה-prefix, הקוד בלקוח יקבל undefined.
4. דומיין מותאם אישית
Settings → Domains → הוסף את הדומיין שלך. Vercel ייתן לך רישומי DNS להגדיר אצל הרשם. אחרי שהתפשטו (זה עניין של דקות עד שעות), תקבל אוטומטית תעודת SSL מ-Let's Encrypt.
5. Preview URLs
כל branch ב-Git מקבל URL נפרד. תפתח PR — Vercel יבנה preview אוטומטי ויפרסם תגובה ב-PR עם הקישור. זה ה-flow הרגיל: אתה דוחף שינוי, מקבל URL לבדיקה, אם בסדר — merge ל-main, וה-main נדחף לפרודקשן.
6. Rolling Releases (אופציונלי)
ב-Pro ו-Enterprise plans, Vercel מציעים Rolling Releases — פריסת canary. אתה דוחף לפרודקשן, אבל רק 5% מהמשתמשים מקבלים את הגרסה החדשה. רואה ש-metrics בסדר — מעלה ל-25%, ואז 100%. אם משהו נשבר — חוזר אחורה מיידית. ראה Rolling Releases docs.
זה לא חובה — לפרויקט קטן אתה לא צריך את זה. אבל שווה לדעת שזה קיים.
שגיאות נפוצות
| שגיאה | מה זה אומר | פתרון |
|---|---|---|
| Hydration mismatch | ה-HTML מהשרת לא תואם למה ש-React מרנדר בלקוח | בדוק שאין Date.now(), Math.random(), או localStorage ב-render של Server Component. הזיז אותם ל-useEffect ב-Client Component |
| You're importing a Server Component into a Client Component | ייבאת קומפוננט server לתוך client ישירות | תעביר כ-children prop במקום ב-import |
"use client" directive must be at the top | שמת אותו אחרי import או בתוך פונקציה | שורה ראשונה לפני הכל, בתוך מירכאות |
useState is not defined | משתמש ב-hook בלי "use client" | תוסיף "use client" בראש הקובץ |
| process.env.MY_VAR is undefined בדפדפן | משתנה סביבה בלי NEXT_PUBLIC_ prefix | שנה את השם ל-NEXT_PUBLIC_MY_VAR ובנה מחדש |
params is a Promise but you used it directly | Next 16 — params הוא Promise | תוסיף await params בתחילת הפונקציה |
| Cache miss בכל הבקשות | "use cache" בלי cacheComponents: true | הוסף cacheComponents: true ב-next.config.ts |
Module not found: Can't resolve 'fs' | קוד Node.js מיובא ל-Client Component | הזיז את ה-import לקובץ עם logic של server בלבד |
middleware is deprecated | אתה ב-Next 16 עם קובץ middleware.ts ישן | שנה שם ל-proxy.ts ושנה את שם הפונקציה ל-proxy. או הרץ את ה-codemod: npx @next/codemod@canary upgrade latest |
| Build נכשל עם Webpack error | יש לך webpack config בפרויקט | הרץ next build --webpack, או העבר את ה-config ל-Turbopack |
| 404 בעמוד שיצרת | חסר page.tsx בתיקייה, או טעות בשם | וודא שהקובץ נקרא בדיוק page.tsx עם export default |
מתי לא להשתמש ב-Next.js
חוזרים לנקודה מהפתיחה — Next.js הוא כלי חזק, אבל הוא לא בחירת ברירת המחדל לכל פרויקט.
אל תשתמש ב-Next.js אם:
- האתר שלך הוא דף נחיתה סטטי בלי backend
- אתה בונה פורטפוליו או blog פשוט (Astro או Vite יעבדו טוב יותר)
- אתה לא רוצה להיות תלוי ב-Vercel (אפשר לפרוס Next ב-Node.js רגיל, אבל אתה מאבד הרבה אופטימיזציות)
- אתה לא מתכוון לכתוב backend logic — אז הסיבוב מ-Vite ל-Next רק יוסיף מורכבות
עבור לפרויקטים סטטיים — קרא את המדריך Vercel vs Firebase Hosting. שניהם חינמיים, שניהם מהירים, ההבדל הוא בסביבת הפיתוח ובמהירות העלאה.
עבור לפרויקטים full-stack קטנים — Next.js + Vercel + Postgres (דרך Neon או Supabase) זה stack ש-Solo developer יכול לתחזק לבד למרחק רב. אם אתה בונה MVP של SaaS על הבסיס הזה, המדריך SaaS MVP 2026 עובר על מה כן ומה לא להשקיע בו בגרסה הראשונה.
ואם אתה שוקל לבנות חלק גדול מהפרויקט בעזרת כלי AI — קרא קודם את מתי כדאי vibe coding (ומתי לא), כי App Router עם Server Components זה בדיוק סוג הקוד ש-AI נוטה לקלקל בשקט (מערבב "use client" במקום הלא נכון, שוכח await על params).
שאלות נפוצות
האם כדאי לעבור מ-Vite ל-Next.js 16 App Router?
תלוי לגמרי במה שאתה בונה. אם יש לך SPA קיים בלי backend אמיתי — פורטפוליו, dashboard פנימי, MVP מהיר — אין סיבה לעבור, ה-migration רק יוסיף מורכבות. אם אתה כותב fetch('/api/...') הרבה, צריך SEO, או מתחיל לנהל שני פרויקטים (frontend ו-backend) בנפרד — אז App Router יחסוך לך כאב ראש משמעותי. עבור אתר קיים, רוב הזמן עדיף להתחיל פרויקט Next חדש ולהעביר קומפוננטים בהדרגה, ולא להמיר את הקיים בבת אחת.
מה ההבדל בין Server Component ל-Client Component?
Server Component רץ על השרת, לא נשלח כ-JavaScript לדפדפן, ויכול להיות async ולקרוא ישירות ל-DB או ל-API. זו ברירת המחדל בכל קובץ ב-app/. Client Component הוא קובץ שמתחיל ב-"use client" — הוא נשלח ל-bundle, מקבל hydrate, ויכול להשתמש ב-useState, ב-event handlers וב-browser APIs כמו window. הכלל המעשי: בנה את העץ כ-Server, ושים Client Components רק בעלים שצריכים אינטראקטיביות.
האם צריך cacheComponents: true כדי שהאתר יעבוד?
לא — אתר ב-Next 16 עובד מצוין בלי קאשינג בכלל, פשוט הכל יהיה דינמי ויקרא ל-DB בכל בקשה. הדגל cacheComponents: true ב-next.config.ts נדרש רק אם אתה רוצה להשתמש בדירקטיבה "use cache" ובמודל PPR. אם תכתוב "use cache" בלי הדגל, תקבל cache miss בכל הבקשות. התחל בלי הדגל, והפעל אותו כשתגיע לדף כבד שבאמת צריך קאש.
למה params ו-cookies() הם עכשיו Promise?
ב-Next 16 ה-APIs האסינכרוניים — params, searchParams, cookies(), headers(), draftMode() — הם Promises שצריך לעשות להם await. זה מאפשר ל-Next להתחיל לרנדר את הדף לפני שהבקשה נפתרה במלואה, ולשלב טוב יותר עם streaming ו-PPR. ב-Next 15 זה היה אופציונלי עם אזהרה, ב-16 זה שובר אם לא תוסיף await. ה-codemod של Next מתקן את רוב המקרים אוטומטית.
מה ההבדל בין revalidateTag ל-updateTag ב-Next 16?
updateTag (חדש ב-16) נותן read-your-writes — המשתמש שביצע את הפעולה רואה את התוצאה המעודכנת מיד, מה שמושלם אחרי שליחת טופס או יצירת רשומה. revalidateTag('posts', 'max') נותן stale-while-revalidate — המבקר רואה את הגרסה הישנה מהקאש בזמן שעדכון רץ ברקע, מה שמתאים לתוכן שלא חייב להיות עדכני לשנייה. שים לב ש-revalidateTag בלי הארגומנט השני deprecated ב-16.
אפשר לפרוס Next.js 16 בלי Vercel?
כן. אפשר להריץ next build && next start על כל שרת Node.js 20.9+, או לארוז ב-Docker ולפרוס ב-Railway, Render, Fly.io או VPS משלך. החיסרון: אתה מאבד חלק מהאופטימיזציות שמגיעות חינם ב-Vercel — Image Optimization, edge caching אוטומטי, Preview URLs לכל branch, ו-Rolling Releases. לדף נחיתה סטטי שווה דווקא לשקול את הדיון בVercel מול Firebase Hosting. לאפליקציית full-stack, Vercel עדיין הדרך עם הכי פחות חיכוך.
אפשר לעשות אימות משתמשים בתוך proxy.ts?
אפשר, אבל לרוב לא כדאי לעשות שם את כל העבודה. proxy.ts רץ על כל בקשה, אז lookup מלא של משתמש מול DB יאט את כל האתר. הדפוס המומלץ: בדוק ב-proxy.ts רק אם קיים cookie של session (בדיקה זריזה, בלי DB), והפנה ל-login אם אין. את האימות המלא — שליפת המשתמש, בדיקת הרשאות — עשה בתוך ה-Server Component או ה-Server Action שמטפל בדף עצמו. מאחר ש-proxy.ts רץ ב-Next 16 על Node.js runtime, יש לך שם גישה ל-crypto המובנה לאימות חתימת JWT זריזה, וזה מספיק לרוב המקרים.
לסיכום
Next.js 16 App Router הוא קפיצת מדרגה אמיתית, אבל הוא לא חינמי בעלות הלימוד. אם תזכור שלושה דברים, חצי מהבלבול נעלם: ברירת המחדל היא Server Component ו-"use client" הוא גבול ולא תווית; הקאשינג הוא opt-in דרך "use cache" והכל דינמי עד שתחליט אחרת; ו-Server Actions מייתרים את רוב ה-/api routes שהיית כותב פעם.
הדרך הנכונה ללמוד את זה היא לא לקרוא עוד — אלא לפתוח טרמינל, להריץ npx create-next-app@latest, לבנות שלושה routes, להוסיף Server Action אחד שכותב ל-DB, ולפרוס ל-Vercel. תוך ערב אחד יהיה לך פרויקט אמיתי שעובד, ותבין מבפנים את כל מה שקראת כאן.
רוצה לבנות את זה איתי? אם זה יותר מדי, או שאתה רוצה תוצאה ב-3 שבועות במקום ב-3 חודשים — דבר איתי. 30 דקות שיחה חינם.
מקורות
- Next.js 16 Release Blog Post
- Upgrading to Next.js 16 — Migration Guide
- Server and Client Components — Official Docs
use cacheDirective ReferencecacheComponentsConfig ReferenceupdateTagFunction ReferencerevalidatePathFunction Reference- Server Actions and Mutations
proxy.tsFile Convention- Turbopack API Reference
- Vercel Rolling Releases Documentation
- Next.js on Vercel — Deployment Guide