Next.js 15: فریمورک نهایی React - راهنمای کامل ۲۰۲۶
📊 آمارهای کلیدی Next.js در ۲۰۲۶:
- 🌟 بیش از ۱۳۰ هزار ستاره در گیتهاب
- 🏢 استفاده توسط غولهای تکنولوژی: Netflix, TikTok, Twitch, Notion, Hulu
- 📦 بیش از ۱۵ میلیون دانلود ماهانه از npm
- ⚡ بهبود ۴۰٪ عملکرد در نسخه ۱۵ نسبت به نسخه ۱۴
- 🔧 بیش از ۱۰۰ هزار وبسایت ساخته شده با Next.js
- 📈 رشد ۳۰۰٪ استفاده در ۳ سال اخیر
🎯 خلاصه اجرایی:
Next.js قدرتمندترین فریمورک React برای تولید برنامههای وب مدرن است که توسط شرکت Vercel توسعه و نگهداری میشود. Next.js با ارائه قابلیتهایی مانند رندرینگ سمت سرور (SSR)، تولید سایتهای ایستا (SSG)، بازتولید تدریجی (ISR)، مسیریابی خودکار و بهینهسازی تصاویر، تجربه توسعهدهندگان و عملکرد برنامهها را به سطح جدیدی رسانده است. نسخه ۱۵ Next.js با معرفی App Router به عنوان پایدار، Turbopack سریعتر، و بهبودهای چشمگیر در Server Components، به انتخاب اول شرکتهای بزرگ برای توسعه وب تبدیل شده است. در این مقاله از درخت کد، به بررسی عمیق این فریمورک قدرتمند میپردازیم.
مقدمه: چرا Next.js انقلابی در توسعه وب ایجاد کرد؟
قبل از Next.js، توسعهدهندگان React با چالشهای بزرگی روبرو بودند: سئوی ضعیف (به دلیل رندرینگ سمت کلاینت)، زمان بارگذاری اولیه بالا، مسیریابی پیچیده و عدم وجود ساختار مشخص برای پروژههای بزرگ. Next.js توسط Guillermo Rauch (بنیانگذار Zeit، حالا Vercel) با یک ایده ساخته شد: "چارچوبی که تمام نیازهای یک پروژه React را در یکپکیج ارائه دهد."
یادم میآید اولین بار در سال ۲۰۱۸ وقتی Next.js 7 منتشر شد، در پروژه فروشگاه اینترنتی از آن استفاده کردم. تفاوت با Create React App مانند شب و روز بود. سئو به طور خودکار بهبود یافت، صفحهها با سرعت باورنکردنی لود میشدند و تجربه توسعهدهندگی فوقالعادهای داشت. امروز پس از سالها توسعه پروژههای بزرگ با Next.js و تدریس آن در درخت کد، میتوانم بگویم Next.js استاندارد طلایی توسعه وب با React است.
۱.۱ تاریخچه نسخههای Next.js
۲۰۱۶: Next.js 1
اولین نسخه با SSR و مسیریابی خودکار
۲۰۱۷: Next.js 3
معرفی قابلیت Static Export
۲۰۱۸: Next.js 7
بهبود عملکرد و پشتیبانی از Webpack 4
۲۰۱۹: Next.js 9
معرفی SSG و API Routes
۲۰۲۰: Next.js 10
بهینهسازی تصاویر و i18n
۲۰۲۱: Next.js 12
معرفی Middleware و SWC
۲۰۲۲: Next.js 13
انقلاب App Router و Server Components (بتا)
۲۰۲۳: Next.js 14
بهبود Turbopack و Server Actions
۲۰۲۴-۲۰۲۶: Next.js 15
App Router پایدار، Turbopack سریعتر، بهبودهای عملکردی
بخش اول: مقایسه Next.js با سایر رویکردها
۱.۱ مقایسه با Create React App (SPA)
| ویژگی | Next.js | Create React App |
|---|---|---|
| رندرینگ | ✅ SSR, SSG, ISR, CSR | ❌ فقط CSR |
| سئو (SEO) | ✅ عالی | ⚠️ ضعیف |
| زمان بارگذاری اولیه | ✅ بسیار سریع | ⚠️ کند |
| مسیریابی | ✅ خودکار بر اساس فایلها | ❌ نیاز به React Router |
| API Routes | ✅ داخلی | ❌ نیاز به سرور جداگانه |
| بهینهسازی تصاویر | ✅ داخلی | ❌ نیاز به کتابخانه |
۱.۲ مقایسه با سایر فریمورکهای React
| ویژگی | Next.js | Remix | Gatsby |
|---|---|---|---|
| محبوبیت | ⭐️⭐️⭐️⭐️⭐️ | ⭐️⭐️⭐️ | ⭐️⭐️⭐️ |
| نوع رندرینگ | SSR, SSG, ISR | SSR | SSG |
| منحنی یادگیری | ✅ متوسط | ⚠️ پیچیده | ✅ آسان |
| اکوسیستم | ⭐ بسیار بزرگ | ⭐ متوسط | ⭐ بزرگ |
| قابلیت مقیاسپذیری | ✅ عالی | ✅ خوب | ⚠️ محدود |
بخش دوم: معماری Next.js 15
۲.۱ App Router vs Pages Router
مقایسه دو رویکرد مسیریابی:
Pages Router (سنتی) App Router (مدرن)
├── pages/ ├── app/
│ ├── index.js │ ├── page.js
│ ├── about.js │ ├── about/
│ ├── products/ │ │ └── page.js
│ │ ├── index.js │ ├── products/
│ │ └── [id].js │ │ ├── page.js
│ └── api/ │ │ └── [id]/
│ └── hello.js │ │ └── page.js
│ └── api/
│ └── hello/
│ └── route.js
مزایای App Router:
- ✅ پشتیبانی از React Server Components به صورت پیشفرض
- ✅ مسیریابی تو در تو (Nested Routes) با layoutهای قابل استفاده مجدد
- ✅ loading و error boundaries اختصاصی برای هر مسیر
- ✅ پشتیبانی از موازیسازی (Parallel Routes) و اینترسپشن (Intercepting Routes)
- ✅ بهبود عملکرد با streaming و partial rendering
بخش سوم: شروع کار با Next.js 15
۳.۱ نصب و راهاندازی
ایجاد پروژه جدید با Next.js 15:
# نصب با npm
npx create-next-app@latest my-app
# نصب با yarn
yarn create next-app my-app
# نصب با pnpm
pnpm create next-app my-app
# سوالات نصب
✔ Would you like to use TypeScript؟ Yes
✔ Would you like to use ESLint؟ Yes
✔ Would you like to use Tailwind CSS؟ Yes
✔ Would you like to use `src/` directory؟ Yes
✔ Would you like to use App Router؟ Yes
✔ Would you like to customize the default import alias؟ No
cd my-app
npm run dev
ساختار پروژه Next.js 15 با App Router:
my-app/
├── src/
│ ├── app/
│ │ ├── favicon.ico
│ │ ├── globals.css
│ │ ├── layout.js # لایه اصلی
│ │ ├── page.js # صفحه اصلی
│ │ ├── about/
│ │ │ └── page.js # صفحه درباره ما
│ │ ├── products/
│ │ │ ├── page.js # لیست محصولات
│ │ │ └── [id]/
│ │ │ └── page.js # صفحه جزئیات محصول
│ │ └── api/
│ │ └── products/
│ │ └── route.js # API محصولات
│ └── components/
│ ├── Header.jsx
│ └── Footer.jsx
├── public/
├── next.config.js
├── package.json
└── README.md
۳.۲ اولین صفحه با Next.js
src/app/page.js - صفحه اصلی:
import Link from 'next/link';
export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-center p-24">
<h1 className="text-4xl font-bold mb-8">
به Next.js 15 خوش آمدید!
</h1>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
<FeatureCard
title="رندرینگ پیشرفته"
description="SSR، SSG، ISR و CSR - هر چه نیاز دارید"
/>
<FeatureCard
title="Server Components"
description="کامپوننتهای سمت سرور با قابلیتهای شگفتانگیز"
/>
<FeatureCard
title="بهینهسازی خودکار"
description="تصاویر، فونتها، اسکریپتها - همه چیز بهینه میشود"
/>
</div>
<div className="mt-12">
<Link
href="/about"
className="bg-blue-500 text-white px-6 py-3 rounded-lg hover:bg-blue-600"
>
درباره ما
</Link>
</div>
</main>
);
}
function FeatureCard({ title, description }) {
return (
<div className="p-6 border rounded-lg shadow-sm">
<h2 className="text-xl font-semibold mb-2">{title}</h2>
<p className="text-gray-600">{description}</p>
</div>
);
}
src/app/layout.js - لایه اصلی:
import './globals.css';
import { Vazirmatn } from 'next/font/google';
const vazirmatn = Vazirmatn({ subsets: ['arabic'] });
export const metadata = {
title: {
default: 'درخت کد | آموزش Next.js',
template: '%s | درخت کد'
},
description: 'آموزش کامل Next.js 15 - فریمورک نهایی React',
keywords: ['Next.js', 'React', 'آموزش', 'برنامهنویسی'],
authors: [{ name: 'محمدمهدی محمودی' }],
openGraph: {
title: 'آموزش Next.js 15',
description: 'دوره کامل آموزش Next.js 15',
siteName: 'درخت کد',
locale: 'fa_IR',
type: 'website',
},
};
export default function RootLayout({ children }) {
return (
<html lang="fa" dir="rtl">
<body className={vazirmatn.className}>
<Header />
<main>{children}</main>
<Footer />
</body>
</html>
);
}
بخش چهارم: روشهای رندرینگ در Next.js 15
۴.۱ Server-Side Rendering (SSR)
رندرینگ سمت سرور با dynamic rendering:
// app/products/page.jsx
// این صفحه در هر درخواست، روی سرور رندر میشود
export const dynamic = 'force-dynamic';
// یا
export const revalidate = 0;
async function getProducts() {
const res = await fetch('https://api.example.com/products', {
cache: 'no-store' // غیرفعال کردن کش
});
if (!res.ok) {
throw new Error('خطا در دریافت محصولات');
}
return res.json();
}
export default async function ProductsPage() {
const products = await getProducts();
return (
<div>
<h1>محصولات ({products.length})</h1>
<div className="grid">
{products.map(product => (
<div key={product.id} className="product-card">
<h3>{product.name}</h3>
<p>{product.price} تومان</p>
</div>
))}
</div>
</div>
);
}
۴.۲ Static Site Generation (SSG)
تولید صفحات ایستا در زمان build:
// app/products/page.jsx
// این صفحه در زمان build تولید میشود
async function getProducts() {
const res = await fetch('https://api.example.com/products', {
cache: 'force-cache' // کش دائمی
});
return res.json();
}
export default async function ProductsPage() {
const products = await getProducts();
return (
<div>
<h1>محصولات</h1>
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
// app/products/[id]/page.jsx
// تولید صفحات داینامیک در زمان build
export async function generateStaticParams() {
const products = await getProducts();
return products.map(product => ({
id: product.id.toString()
}));
}
export default async function ProductPage({ params }) {
const product = await getProduct(params.id);
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
</div>
);
}
۴.۳ Incremental Static Regeneration (ISR)
بازتولید تدریجی صفحات:
// app/products/[id]/page.jsx
// این صفحه هر 60 ثانیه یک بار بازتولید میشود
export const revalidate = 60;
async function getProduct(id) {
const res = await fetch(`https://api.example.com/products/${id}`, {
next: {
revalidate: 60 // بازتولید هر 60 ثانیه
}
});
return res.json();
}
export default async function ProductPage({ params }) {
const product = await getProduct(params.id);
return (
<div>
<h1>{product.name}</h1>
<p>آخرین بهروزرسانی: {new Date().toLocaleString('fa-IR')}</p>
<p>{product.description}</p>
</div>
);
}
۴.۴ Client-Side Rendering (CSR)
رندرینگ سمت کلاینت با 'use client':
// app/dashboard/page.jsx
'use client';
import { useState, useEffect } from 'react';
export default function Dashboard() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('/api/dashboard')
.then(res => res.json())
.then(data => {
setData(data);
setLoading(false);
});
}, []);
if (loading) return <div>در حال بارگذاری...</div>;
return (
<div>
<h1>داشبورد</h1>
<p>تعداد کاربران: {data.users}</p>
<p>فروش امروز: {data.sales}</p>
</div>
);
}
بخش پنجم: Server Components vs Client Components
۵.۱ تفاوتهای کلیدی
| ویژگی | Server Components | Client Components |
|---|---|---|
| محل اجرا | ✅ سرور | ✅ کلاینت |
| دسترسی به دیتابیس | ✅ مستقیم | ❌ از طریق API |
| Hooks (useState, useEffect) | ❌ ندارد | ✅ دارد |
| تعامل کاربر | ❌ ندارد | ✅ دارد |
| حجم جاوااسکریپت | ✅ صفر | ⚠️ دارد |
| سئو | ✅ عالی | ⚠️ وابسته به SSR |
ترکیب Server و Client Components:
// app/products/page.jsx - Server Component (پیشفرض)
import ProductList from '@/components/ProductList';
import { getProducts } from '@/lib/products';
export default async function ProductsPage() {
const products = await getProducts();
return (
<div>
<h1>محصولات ما</h1>
<ProductList initialProducts={products} />
</div>
);
}
// components/ProductList.jsx - Client Component
'use client';
import { useState } from 'react';
export default function ProductList({ initialProducts }) {
const [products, setProducts] = useState(initialProducts);
const [filter, setFilter] = useState('');
const filteredProducts = products.filter(p =>
p.name.includes(filter)
);
return (
<div>
<input
type="text"
value={filter}
onChange={(e) => setFilter(e.target.value)}
placeholder="جستجوی محصول..."
/>
<div className="grid">
{filteredProducts.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
</div>
);
}
بخش ششم: پروژههای عملی با Next.js
۶.۱ نمونه ۱: وبلاگ با Next.js
سیستم وبلاگ با Markdown:
// lib/posts.js
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
const postsDirectory = path.join(process.cwd(), 'posts');
export async function getPosts() {
const fileNames = fs.readdirSync(postsDirectory);
const posts = fileNames.map(fileName => {
const slug = fileName.replace(/\.md$/, '');
const fullPath = path.join(postsDirectory, fileName);
const fileContents = fs.readFileSync(fullPath, 'utf8');
const { data, content } = matter(fileContents);
return {
slug,
title: data.title,
date: data.date,
excerpt: data.excerpt,
content
};
});
return posts.sort((a, b) => (a.date < b.date ? 1 : -1));
}
export async function getPost(slug) {
const fullPath = path.join(postsDirectory, `${slug}.md`);
const fileContents = fs.readFileSync(fullPath, 'utf8');
const { data, content } = matter(fileContents);
return {
slug,
title: data.title,
date: data.date,
excerpt: data.excerpt,
content
};
}
// app/blog/page.jsx
import Link from 'next/link';
import { getPosts } from '@/lib/posts';
export default async function BlogPage() {
const posts = await getPosts();
return (
<div className="container mx-auto px-4">
<h1 className="text-3xl font-bold mb-8">وبلاگ درخت کد</h1>
<div className="grid gap-8">
{posts.map(post => (
<article key={post.slug} className="border rounded-lg p-6">
<h2 className="text-xl font-semibold mb-2">
<Link href={`/blog/${post.slug}`}>
{post.title}
</Link>
</h2>
<p className="text-gray-600 mb-2">
{new Date(post.date).toLocaleDateString('fa-IR')}
</p>
<p className="mb-4">{post.excerpt}</p>
<Link
href={`/blog/${post.slug}`}
className="text-blue-500 hover:underline"
>
ادامه مطلب
</Link>
</article>
))}
</div>
</div>
);
}
// app/blog/[slug]/page.jsx
import { getPost, getPosts } from '@/lib/posts';
import { notFound } from 'next/navigation';
import Markdown from 'react-markdown';
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map(post => ({ slug: post.slug }));
}
export default async function BlogPostPage({ params }) {
const post = await getPost(params.slug);
if (!post) {
notFound();
}
return (
<article className="container mx-auto px-4 py-8">
<h1 className="text-3xl font-bold mb-4">{post.title}</h1>
<p className="text-gray-600 mb-8">
{new Date(post.date).toLocaleDateString('fa-IR')}
</p>
<div className="prose prose-lg">
<Markdown>{post.content}</Markdown>
</div>
</article>
);
}
۶.۲ نمونه ۲: فروشگاه اینترنتی با Next.js
سیستم سبد خرید و محصولات:
// app/products/page.jsx
import { getProducts } from '@/lib/products';
import ProductGrid from '@/components/ProductGrid';
export const metadata = {
title: 'محصولات | فروشگاه درخت کد',
description: 'لیست تمام محصولات فروشگاه',
};
export default async function ProductsPage() {
const products = await getProducts();
return (
<div className="container mx-auto px-4">
<h1 className="text-3xl font-bold mb-8">محصولات</h1>
<ProductGrid products={products} />
</div>
);
}
// components/ProductGrid.jsx
'use client';
import { useState } from 'react';
import { useCart } from '@/context/CartContext';
import Image from 'next/image';
export default function ProductGrid({ products }) {
const [filteredProducts, setFilteredProducts] = useState(products);
const [category, setCategory] = useState('all');
const { addToCart } = useCart();
const categories = [...new Set(products.map(p => p.category))];
const filterByCategory = (cat) => {
setCategory(cat);
if (cat === 'all') {
setFilteredProducts(products);
} else {
setFilteredProducts(products.filter(p => p.category === cat));
}
};
return (
<div>
<div className="flex gap-2 mb-6">
<button
onClick={() => filterByCategory('all')}
className={`px-4 py-2 rounded ${
category === 'all' ? 'bg-blue-500 text-white' : 'bg-gray-200'
}`}
>
همه
</button>
{categories.map(cat => (
<button
key={cat}
onClick={() => filterByCategory(cat)}
className={`px-4 py-2 rounded ${
category === cat ? 'bg-blue-500 text-white' : 'bg-gray-200'
}`}
>
{cat}
</button>
))}
</div>
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-6">
{filteredProducts.map(product => (
<div key={product.id} className="border rounded-lg p-4">
<Image
src={product.image}
alt={product.name}
width={200}
height={200}
className="w-full h-48 object-cover mb-4"
/>
<h3 className="font-semibold mb-2">{product.name}</h3>
<p className="text-gray-600 mb-2">{product.price} تومان</p>
<button
onClick={() => addToCart(product)}
className="w-full bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
>
افزودن به سبد خرید
</button>
</div>
))}
</div>
</div>
);
}
// context/CartContext.jsx
'use client';
import { createContext, useContext, useState } from 'react';
const CartContext = createContext();
export function CartProvider({ children }) {
const [cart, setCart] = useState([]);
const addToCart = (product) => {
setCart(prev => {
const existing = prev.find(item => item.id === product.id);
if (existing) {
return prev.map(item =>
item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
);
}
return [...prev, { ...product, quantity: 1 }];
});
};
const removeFromCart = (id) => {
setCart(prev => prev.filter(item => item.id !== id));
};
const updateQuantity = (id, quantity) => {
setCart(prev =>
prev.map(item =>
item.id === id ? { ...item, quantity } : item
)
);
};
const total = cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
const itemCount = cart.reduce((sum, item) => sum + item.quantity, 0);
return (
<CartContext.Provider value={{
cart,
addToCart,
removeFromCart,
updateQuantity,
total,
itemCount
}}>
{children}
</CartContext.Provider>
);
}
export function useCart() {
const context = useContext(CartContext);
if (!context) {
throw new Error('useCart must be used within CartProvider');
}
return context;
}
بخش هفتم: API Routes و Server Actions
۷.۱ API Routes در Next.js
ایجاد API در App Router:
// app/api/products/route.js
import { getProducts, createProduct } from '@/lib/products';
import { NextResponse } from 'next/server';
export async function GET() {
try {
const products = await getProducts();
return NextResponse.json(products);
} catch (error) {
return NextResponse.json(
{ error: 'خطا در دریافت محصولات' },
{ status: 500 }
);
}
}
export async function POST(request) {
try {
const body = await request.json();
const newProduct = await createProduct(body);
return NextResponse.json(newProduct, { status: 201 });
} catch (error) {
return NextResponse.json(
{ error: 'خطا در ایجاد محصول' },
{ status: 500 }
);
}
}
// app/api/products/[id]/route.js
export async function GET(request, { params }) {
const product = await getProduct(params.id);
if (!product) {
return NextResponse.json(
{ error: 'محصول یافت نشد' },
{ status: 404 }
);
}
return NextResponse.json(product);
}
۷.۲ Server Actions در Next.js 15
مدیریت فرم با Server Actions:
// app/contact/page.jsx
'use server';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
async function submitContact(formData) {
'use server';
const name = formData.get('name');
const email = formData.get('email');
const message = formData.get('message');
// اعتبارسنجی
if (!name || !email || !message) {
return {
error: 'همه فیلدها الزامی هستند'
};
}
// ذخیره در دیتابیس
await saveContact({ name, email, message });
// ارسال ایمیل
await sendEmail({ name, email, message });
// بازتولید کش
revalidatePath('/contact');
// هدایت
redirect('/contact/success');
}
export default function ContactPage() {
return (
<form action={submitContact} className="max-w-md mx-auto">
<div className="mb-4">
<label className="block mb-2">نام:</label>
<input
type="text"
name="name"
className="w-full border rounded p-2"
required
/>
</div>
<div className="mb-4">
<label className="block mb-2">ایمیل:</label>
<input
type="email"
name="email"
className="w-full border rounded p-2"
required
/>
</div>
<div className="mb-4">
<label className="block mb-2">پیام:</label>
<textarea
name="message"
rows="4"
className="w-full border rounded p-2"
required
></textarea>
</div>
<button
type="submit"
className="bg-blue-500 text-white px-6 py-2 rounded hover:bg-blue-600"
>
ارسال پیام
</button>
</form>
);
}
بخش هشتم: بهینهسازی و سئو در Next.js
۸.۱ بهینهسازی تصاویر با next/image
استفاده از Image Component:
import Image from 'next/image';
export default function OptimizedImage() {
return (
<div>
<!-- تصویر محلی -->
<Image
src="/profile.jpg"
alt="پروفایل"
width={400}
height={400}
className="rounded-full"
priority // برای تصاویر بالای صفحه
/>
<!-- تصویر از راه دور -->
<Image
src="https://example.com/image.jpg"
alt="تصویر از راه دور"
width={800}
height={600}
quality={90} // کیفیت تصویر
placeholder="blur" // نمایش بلور در حال لود
blurDataURL="data:image/jpeg;base64,..." // تصویر بلور
/>
<!-- تصویر واکنشگرا -->
<Image
src="/hero.jpg"
alt="hero"
fill
className="object-cover"
sizes="(max-width: 768px) 100vw, 50vw"
/>
</div>
);
}
۸.۲ بهینهسازی فونت با next/font
استفاده از فونتهای بهینه:
// app/layout.js
import { Vazirmatn } from 'next/font/google';
const vazirmatn = Vazirmatn({
subsets: ['arabic'],
display: 'swap', // بهبود عملکرد
variable: '--font-vazirmatn', // متغیر CSS
weight: ['400', '500', '600', '700'], // وزنهای مورد نیاز
});
export default function RootLayout({ children }) {
return (
<html lang="fa" dir="rtl" className={vazirmatn.variable}>
<body style={{ fontFamily: 'var(--font-vazirmatn)' }}>
{children}
</body>
</html>
);
}
۸.۳ سئو با Metadata API
مدیریت متادیتا برای سئو:
// app/products/[id]/page.jsx
import { getProduct } from '@/lib/products';
export async function generateMetadata({ params }) {
const product = await getProduct(params.id);
return {
title: product.name,
description: product.description,
keywords: product.tags,
openGraph: {
title: product.name,
description: product.description,
images: [
{
url: product.image,
width: 800,
height: 600,
alt: product.name,
},
],
},
twitter: {
card: 'summary_large_image',
title: product.name,
description: product.description,
images: [product.image],
},
alternates: {
canonical: `https://treec.net/products/${params.id}`,
},
};
}
export default function ProductPage({ params }) {
// ...
}
بخش نهم: Middleware و احراز هویت
Middleware برای محافظت از مسیرها:
// middleware.js
import { NextResponse } from 'next/server';
import { verifyToken } from '@/lib/auth';
export function middleware(request) {
const token = request.cookies.get('token')?.value;
const { pathname } = request.nextUrl;
// مسیرهای عمومی
const publicPaths = ['/', '/about', '/products', '/login', '/register'];
const isPublicPath = publicPaths.some(path =>
pathname.startsWith(path)
);
// مسیرهای محافظت شده
const protectedPaths = ['/dashboard', '/profile', '/admin'];
const isProtectedPath = protectedPaths.some(path =>
pathname.startsWith(path)
);
// اگر مسیر محافظت شده است و توکن وجود ندارد
if (isProtectedPath && !token) {
const url = new URL('/login', request.url);
url.searchParams.set('redirect', pathname);
return NextResponse.redirect(url);
}
// اگر کاربر وارد شده و به صفحه لاگین میرود
if (pathname === '/login' && token) {
return NextResponse.redirect(new URL('/dashboard', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: [
'/*',
'/dashboard/:path*',
'/profile/:path*',
'/admin/:path*',
],
};
بخش دهم: دیپلوی و استقرار
۱۰.۱ دیپلوی روی Vercel
استقرار در Vercel (سادهترین روش):
# نصب Vercel CLI
npm i -g vercel
# دیپلوی پروژه
vercel
# دیپلوی برای production
vercel --prod
# یا اتصال به GitHub و دیپلوی خودکار
1. push کد به GitHub
2. import پروژه در vercel.com
3. دیپلوی خودکار با هر push
۱۰.۲ متغیرهای محیطی
مدیریت environment variables:
// .env.local (توسعه محلی)
DATABASE_URL="mysql://localhost:3306/mydb"
API_KEY="your-api-key"
NEXT_PUBLIC_GA_ID="G-XXXXXXXX" // با NEXT_PUBLIC_ در کلاینت هم قابل دسترس است
// .env.production (محصول)
DATABASE_URL="mysql://production:password@prod-db:3306/proddb"
API_KEY="prod-api-key"
// استفاده در کد
const dbUrl = process.env.DATABASE_URL;
const gaId = process.env.NEXT_PUBLIC_GA_ID;
بخش یازدهم: بازار کار و فرصتهای شغلی
تحلیل بازار کار ایران در ۲۰۲۶:
| موقعیت شغلی | متوسط حقوق (تومان) | تقاضا | مهارتهای مورد نیاز |
|---|---|---|---|
| توسعهدهنده Next.js ارشد | ۷۰-۱۰۰ میلیون | ⭐️⭐️⭐️⭐️⭐️ | Next.js 15، React 19، TypeScript، Tailwind |
| توسعهدهنده Full-Stack | ۸۰-۱۲۰ میلیون | ⭐️⭐️⭐️⭐️⭐️ | Next.js، Node.js، MongoDB، Prisma |
| توسعهدهنده فرانتاند | ۵۰-۸۰ میلیون | ⭐️⭐️⭐️⭐️⭐️ | Next.js، React، TypeScript، REST API |
| معمار وب | ۱۰۰-۱۵۰ میلیون | ⭐️⭐️⭐️⭐️ | Next.js، Microservices، DevOps، Cloud |
فرصتهای شغلی بینالمللی:
- Next.js Developer: $70,000 - $140,000 سالانه
- Senior Full-Stack (Next.js): $90,000 - $170,000 سالانه
- Frontend Architect: $120,000 - $200,000 سالانه
- Remote Next.js Developer: $60,000 - $130,000 سالانه
نتیجهگیری: چرا Next.js؟
Next.js ۱۵ نه فقط یک فریمورک React، بلکه یک پلتفرم کامل برای توسعه وب مدرن است. با ترکیب قدرت React، بهینهسازیهای خودکار، و قابلیتهای بینظیر سمت سرور، Next.js به استاندارد طلایی صنعت تبدیل شده است.
✅ برای مبتدیان:
- شروع آسان با create-next-app
- مستندات عالی و کامل
- جامعه بزرگ و فعال
- منابع آموزشی فراوان
💼 برای کسبوکارها:
- سئوی عالی برای دیده شدن
- عملکرد فوقالعاده و سرعت بالا
- مقیاسپذیری بینظیر
- کاهش هزینههای زیرساخت
🚀 برای توسعهدهندگان:
- بازار کار داغ و پرتقاضا
- حقوقهای بالا در ایران و جهان
- تجربه توسعه لذتبخش
- یادگیری تکنولوژیهای روز
در درخت کد، ما Next.js را به عنوان اصلیترین فریمورک برای پروژههای React خود انتخاب کردهایم. ما همراه شما هستیم تا با آموزشهای تخصصی، پروژههای عملی و مشاورههای فنی، مسیر تبدیل شدن به یک توسعهدهنده حرفهای Next.js را هموار کنیم.
🎯 راهنمای عملی شروع با Next.js 15:
- مطالعه مستندات رسمی: Next.js Documentation
- شرکت در دوره Next.js درخت کد
- ساخت یک پروژه عملی (وبلاگ، فروشگاه، داشبورد)
- یادگیری TypeScript و Tailwind CSS
- دیپلوی روی Vercel و انتشار پروژه