React Router چیست؟ آموزش کامل مسیریابی در React با ساخت داشبورد هوش مصنوعی
در این آموزش React Router را از صفر یاد میگیرید؛ از BrowserRouter، Route و Link تا Layout، Outlet، مسیر پویا، Query String، صفحه 404 و Protected Route. در پایان یک داشبورد هوش مصنوعی چندصفحهای متصل به API درواره میسازیم.
بیشتر اپلیکیشنهای واقعی React فقط یک صفحه ندارند. یک داشبورد هوش مصنوعی ممکن است صفحه اصلی، Playground، فهرست مدلها، تاریخچه درخواستها، تنظیمات حساب، جزئیات مکالمه و صفحه مدیریت مصرف داشته باشد.
اگر برای جابهجایی میان این بخشها از لینک معمولی HTML استفاده کنیم، مرورگر هر بار کل صفحه را از سرور دریافت و برنامه React را از ابتدا اجرا میکند. React Router امکان میدهد URL مرورگر را به Componentهای React متصل کنیم و مسیریابی سمت Client را بدون Reload کامل صفحه انجام دهیم.
اما React Router دقیقاً چیست؟ چه تفاوتی میان react-router و react-router-dom وجود دارد؟ BrowserRouter، Routes، Route و Outlet چه میکنند؟ چگونه مسیر پویا، Query Parameter، صفحه 404 و مسیر محافظتشده بسازیم؟ چرا پس از انتشار، Refresh کردن یک Route ممکن است خطای 404 ایجاد کند؟
در این راهنما React Router را از مفاهیم پایه تا ساخت یک داشبورد هوش مصنوعی واقعی بررسی میکنیم.
React Router چیست؟
React Router یک کتابخانه مسیریابی برای برنامههای React است. این کتابخانه URL مرورگر را با رابط کاربری برنامه هماهنگ میکند.
برای مثال:
/ صفحه اصلی
/playground محیط آزمایش هوش مصنوعی
/models فهرست مدلها
/history تاریخچه درخواستها
/conversations/42 جزئیات مکالمه شماره 42
/settings تنظیمات
React Router بررسی میکند URL فعلی با کدام Route مطابقت دارد و سپس Component مربوط را نمایش میدهد.
برای مثال:
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/models" element={<ModelsPage />} />
<Route path="/settings" element={<SettingsPage />} />
</Routes>
طبق مستندات رسمی React Router، Routeها با اتصال Segmentهای URL به Elementهای رابط کاربری تعریف میشوند.
چرا در React به Router نیاز داریم؟
بدون Router ممکن است برای نمایش صفحات از State استفاده کنید:
function App() {
const [page, setPage] = useState("home");
if (page === "models") {
return <ModelsPage />;
}
if (page === "settings") {
return <SettingsPage />;
}
return <HomePage />;
}
این روش برای یک نمونه بسیار کوچک کار میکند، اما مشکلات مهمی دارد:
- URL تغییر نمیکند.
- کاربر نمیتواند آدرس صفحه را Bookmark کند.
- دکمه Back و Forward مرورگر رفتار طبیعی ندارد.
- اشتراکگذاری لینک یک صفحه ممکن نیست.
- Refresh صفحه وضعیت فعلی را از بین میبرد.
- مسیرهای تودرتو پیچیده میشوند.
- مدیریت Parameterها دشوار است.
- نمایش Layout مشترک تکراری میشود.
React Router این نیازها را در یک ساختار مسیریابی استاندارد مدیریت میکند.
مسیریابی سمت Client چیست؟
در یک وبسایت سنتی، کلیک روی هر لینک معمولاً Request جدیدی به سرور میفرستد:
Browser -> Server -> HTML جدید
در یک Single Page Application یا SPA، فایل اصلی برنامه یکبار بارگذاری میشود و Router بخش مناسب رابط کاربری را براساس URL نمایش میدهد:
Browser
|
v
React Application
|
v
React Router
|
v
Component مربوط به URL
تغییر Route میتواند بدون Reload کامل صفحه انجام شود؛ اما ممکن است Component جدید همچنان برای دریافت داده به Backend درخواست بفرستد.
React Router چه کاری انجام نمیدهد؟
React Router مسئول مسیریابی رابط کاربری است. این کتابخانه جایگزین موارد زیر نیست:
- Backend
- دیتابیس
- احراز هویت سمت سرور
- کنترل واقعی دسترسی
- مدیریت API Key
- مدیریت State عمومی
- دریافت خودکار تمام دادهها در هر Mode
- Server-side Rendering در همه تنظیمات
- مجوزدهی سازمانی
- Rate Limiting
بهخصوص باید بدانید Protected Route در Frontend یک مرز امنیتی واقعی نیست. Backend همچنان باید دسترسی کاربر را برای هر Request بررسی کند.
تفاوت react-router و react-router-dom چیست؟
در نسخههای قدیمیتر، بسیاری از آموزشها Package زیر را نصب میکردند:
npm install react-router-dom
به همین دلیل عبارت react-router-dom هنوز در جستوجوها، پروژههای موجود و آموزشهای اینترنتی بسیار دیده میشود.
در مستندات نسخه فعلی React Router، برای Declarative Mode از Package زیر استفاده میشود:
npm install react-router
و Componentهایی مانند BrowserRouter مستقیماً از آن Import میشوند:
import {
BrowserRouter,
Routes,
Route,
} from "react-router";
راهنمای نصب فعلی در مستندات رسمی React Router همین الگو را ارائه میکند.
اگر پروژه موجود شما از react-router-dom استفاده میکند، بدون بررسی نسخه و Migration Guide نام Packageها و Importها را تغییر ندهید. ابتدا نسخه نصبشده را ببینید:
npm list react-router react-router-dom
سپس مستندات مربوط به همان نسخه را بررسی کنید.
Modeهای مختلف React Router
React Router در نسخههای جدید چند شیوه استفاده دارد:
- Declarative Mode
- Data Mode
- Framework Mode
Declarative Mode
در این روش Routeها با Componentهایی مانند BrowserRouter، Routes و Route تعریف میشوند:
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
</Routes>
</BrowserRouter>
این روش برای یادگیری مفاهیم اصلی و بسیاری از SPAهای React مناسب است.
Data Mode
در Data Mode معمولاً Router با توابعی مانند createBrowserRouter ساخته میشود. این Mode قابلیتهایی مانند Loader، Action، Pending UI و Error Handling مبتنی بر Route را در اختیار برنامه قرار میدهد.
Framework Mode
Framework Mode مجموعه گستردهتری از قابلیتهای Routing، Data Loading، Rendering، Route Module و Deployment را فراهم میکند.
در این مقاله برای شفافیت و کاربرد عمومی، ابتدا Declarative Mode را آموزش میدهیم و در بخش جداگانه نمونهای از Data Router نیز ارائه میکنیم.
پیشنیازها
برای دنبال کردن پروژه به این موارد نیاز دارید:
- Node.js سازگار با Vite و React Router
- npm یا Package Manager مشابه
- آشنایی مقدماتی با JavaScript
- آشنایی پایه با React Component و Hook
- یک ویرایشگر مانند VS Code
نسخههای نصبشده را بررسی کنید:
node --version
npm --version
ساخت پروژه React با Vite
یک پروژه جدید ایجاد کنید:
npm create vite@latest react-router-ai-dashboard -- --template react
وارد پوشه شوید:
cd react-router-ai-dashboard
وابستگیها را نصب کنید:
npm install
React Router را نصب کنید:
npm install react-router
Development Server را اجرا کنید:
npm run dev
اضافه کردن BrowserRouter
فایل src/main.jsx:
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router";
import App from "./App.jsx";
import "./index.css";
createRoot(document.getElementById("root")).render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>
);
BrowserRouter از History API مرورگر برای هماهنگ کردن URL و رابط کاربری استفاده میکند.
تمام Componentهایی که از Hookها یا Componentهای React Router استفاده میکنند باید داخل Router قرار داشته باشند.
این ساختار اشتباه است:
function App() {
const navigate = useNavigate();
return (
<BrowserRouter>
<Home />
</BrowserRouter>
);
}
در این مثال useNavigate پیش از قرار گرفتن Component در Context مربوط به Router اجرا شده است.
ساختار درست:
<BrowserRouter>
<App />
</BrowserRouter>
و سپس داخل App:
function App() {
const navigate = useNavigate();
return <button onClick={() => navigate("/")}>خانه</button>;
}
تعریف اولین Route
فایل src/App.jsx:
import { Route, Routes } from "react-router";
function HomePage() {
return <h1>صفحه اصلی</h1>;
}
function ModelsPage() {
return <h1>مدلهای هوش مصنوعی</h1>;
}
function SettingsPage() {
return <h1>تنظیمات</h1>;
}
export default function App() {
return (
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/models" element={<ModelsPage />} />
<Route path="/settings" element={<SettingsPage />} />
</Routes>
);
}
با رفتن به آدرس /models، Component مربوط به ModelsPage نمایش داده میشود.
Routes و Route چه تفاوتی دارند؟
Routes
Routes مجموعه Routeها را بررسی میکند و بهترین Route مطابق با URL فعلی را نمایش میدهد.
<Routes>
...
</Routes>
Route
هر Route یک Path را به یک Element متصل میکند:
<Route
path="/models"
element={<ModelsPage />}
/>
ویژگی path مسیر و element رابط کاربری مربوط به آن مسیر را مشخص میکند.
استفاده از Link
برای جابهجایی داخلی در برنامه از Link استفاده کنید:
import { Link } from "react-router";
export default function Navigation() {
return (
<nav>
<Link to="/">خانه</Link>
<Link to="/models">مدلها</Link>
<Link to="/settings">تنظیمات</Link>
</nav>
);
}
استفاده از Anchor معمولی:
<a href="/models">مدلها</a>
ممکن است باعث Reload کامل Document شود.
برای لینک داخلی برنامه:
<Link to="/models">مدلها</Link>
برای لینک خارجی همچنان Anchor معمولی مناسب است:
<a
href="https://darvareh.ir/models"
target="_blank"
rel="noreferrer"
>
مشاهده مدلهای درواره
</a>
استفاده از NavLink
NavLink میتواند تشخیص دهد لینک فعلی Active است:
import { NavLink } from "react-router";
export default function Sidebar() {
return (
<nav>
<NavLink
to="/"
end
className={({ isActive }) =>
isActive ? "nav-link active" : "nav-link"
}
>
داشبورد
</NavLink>
<NavLink
to="/playground"
className={({ isActive }) =>
isActive ? "nav-link active" : "nav-link"
}
>
Playground
</NavLink>
<NavLink
to="/models"
className={({ isActive }) =>
isActive ? "nav-link active" : "nav-link"
}
>
مدلها
</NavLink>
</nav>
);
}
ویژگی end روی Route ریشه مهم است. بدون آن ممکن است لینک / برای مسیرهای دیگر نیز Active باقی بماند.
CSS:
.nav-link {
display: block;
padding: 10px 14px;
border-radius: 10px;
color: #566076;
text-decoration: none;
}
.nav-link:hover {
background: #f1f3fb;
}
.nav-link.active {
color: #fff;
background: #6046d7;
}
ساخت Nested Routes
Nested Route یا Route تودرتو زمانی کاربرد دارد که چند صفحه Layout مشترک داشته باشند.
برای مثال:
/dashboard
/dashboard/usage
/dashboard/settings
تعریف Routeها:
<Routes>
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<DashboardHome />} />
<Route path="usage" element={<UsagePage />} />
<Route path="settings" element={<SettingsPage />} />
</Route>
</Routes>
مسیرهای فرزند بهصورت خودکار Path والد را دریافت میکنند:
/dashboard
/dashboard/usage
/dashboard/settings
Outlet چیست؟
Outlet محل نمایش Route فرزند در Layout والد است.
import { NavLink, Outlet } from "react-router";
export default function DashboardLayout() {
return (
<div className="dashboard">
<aside>
<NavLink to="/dashboard" end>
نمای کلی
</NavLink>
<NavLink to="/dashboard/usage">
مصرف
</NavLink>
<NavLink to="/dashboard/settings">
تنظیمات
</NavLink>
</aside>
<main>
<Outlet />
</main>
</div>
);
}
اگر URL برابر /dashboard/usage باشد، UsagePage در محل Outlet نمایش داده میشود.
براساس راهنمای Nested Routes در React Router، Route فرزند در Outlet مربوط به Route والد Render میشود.
Index Route چیست؟
Index Route صفحه پیشفرض یک Route والد است:
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<DashboardHome />} />
<Route path="usage" element={<UsagePage />} />
</Route>
وقتی کاربر وارد /dashboard شود، DashboardHome در Outlet نمایش داده میشود.
Index Route نمیتواند Route فرزند داشته باشد. اگر به ساختار تودرتو نیاز دارید، از Layout Route استفاده کنید.
Layout Route چیست؟
Route بدون path میتواند فقط یک Layout مشترک ایجاد کند:
<Routes>
<Route element={<PublicLayout />}>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
</Route>
<Route element={<DashboardLayout />}>
<Route path="/dashboard" element={<DashboardPage />} />
<Route path="/settings" element={<SettingsPage />} />
</Route>
</Routes>
Layout Route Segment جدیدی به URL اضافه نمیکند.
مسیر پویا یا Dynamic Route
اگر بخشی از Path با : شروع شود، یک Dynamic Segment است.
<Route
path="/conversations/:conversationId"
element={<ConversationPage />}
/>
این Route با آدرسهای زیر مطابقت دارد:
/conversations/1
/conversations/42
/conversations/abc-123
برای خواندن Parameter از useParams استفاده کنید:
import { useParams } from "react-router";
export default function ConversationPage() {
const { conversationId } = useParams();
return (
<section>
<h1>جزئیات مکالمه</h1>
<p>شناسه: {conversationId}</p>
</section>
);
}
Dynamic Segmentها در مستندات Routing توضیح داده شدهاند.
اعتبارسنجی Route Parameter
مقادیر useParams از URL میآیند و قابل اعتماد نیستند. اگر شناسه باید عدد باشد، آن را بررسی کنید:
import { useParams } from "react-router";
export default function ConversationPage() {
const { conversationId } = useParams();
const numericId = Number(conversationId);
if (!Number.isInteger(numericId) || numericId <= 0) {
return <p>شناسه مکالمه معتبر نیست.</p>;
}
return <p>مکالمه شماره {numericId}</p>;
}
Backend نیز باید شناسه و دسترسی کاربر را مستقل از Frontend اعتبارسنجی کند.
چند Parameter در یک Route
<Route
path="/workspaces/:workspaceId/conversations/:conversationId"
element={<ConversationPage />}
/>
خواندن مقادیر:
const {
workspaceId,
conversationId,
} = useParams();
URL نمونه:
/workspaces/team-a/conversations/42
Optional Segment
در نسخههایی که از Optional Segment پشتیبانی میکنند، میتوان علامت ? را به Segment اضافه کرد:
<Route
path="/reports/:reportId?"
element={<ReportsPage />}
/>
این Route ممکن است با هر دو مسیر مطابقت داشته باشد:
/reports
/reports/42
قبل از استفاده، پشتیبانی آن را در نسخه نصبشده بررسی کنید.
Splat Route
برای تطبیق ادامه مسیر میتوان از * استفاده کرد:
<Route
path="/files/*"
element={<FileBrowserPage />}
/>
مثال:
/files/documents/2026/report.pdf
Splat برای File Browser، Documentation و بعضی ساختارهای تودرتو کاربرد دارد؛ اما استفاده بیش از حد از آن میتواند Routeها را مبهم کند.
صفحه 404 در React Router
در انتهای Routeها یک Catch-all Route قرار دهید:
<Route path="*" element={<NotFoundPage />} />
Component صفحه 404:
import { Link } from "react-router";
export default function NotFoundPage() {
return (
<main>
<p>خطای 404</p>
<h1>صفحه موردنظر پیدا نشد</h1>
<Link to="/">
بازگشت به صفحه اصلی
</Link>
</main>
);
}
نکته مهم: این صفحه 404 مربوط به Router سمت Client است. اگر Web Server پیش از اجرای React برای Route ناشناخته 404 برگرداند، باید Rewrite سرور نیز تنظیم شود.
جابهجایی برنامهنویسیشده با useNavigate
گاهی Navigation بعد از یک عملیات انجام میشود:
- بعد از ورود موفق
- بعد از ساخت Resource
- بعد از حذف آیتم
- پس از ثبت تنظیمات
- بعد از انتخاب Workspace
مثال:
import { useNavigate } from "react-router";
export default function CreateConversationButton() {
const navigate = useNavigate();
async function handleCreate() {
const conversation = await createConversation();
navigate(
`/conversations/${conversation.id}`
);
}
return (
<button
type="button"
onClick={handleCreate}
>
مکالمه جدید
</button>
);
}
بازگشت به صفحه قبل:
navigate(-1);
رفتن به صفحه بعد در History:
navigate(1);
جایگزین کردن Entry فعلی:
navigate("/dashboard", {
replace: true,
});
گزینه replace برای مواردی مانند Redirect بعد از Login مفید است تا کاربر با دکمه Back به صفحه Login برنگردد.
چه زمانی Link و چه زمانی useNavigate؟
از Link برای Navigation قابلمشاهده و قابلکلیک استفاده کنید:
<Link to="/models">
مشاهده مدلها
</Link>
از useNavigate زمانی استفاده کنید که Navigation نتیجه یک رویداد یا عملیات است:
await saveSettings();
navigate("/dashboard");
برای تبدیل تمام لینکها به Button و useNavigate دلیل خوبی وجود ندارد. Link از نظر ساختار HTML، دسترسیپذیری و رفتار طبیعی لینک مناسبتر است.
Navigate Component
برای Redirect در Render:
import { Navigate } from "react-router";
function LegacyModelsPage() {
return (
<Navigate
to="/models"
replace
/>
);
}
این روش برای Routeهای قدیمی یا Redirect شرطی قابلاستفاده است.
استفاده از useLocation
useLocation اطلاعات URL فعلی را برمیگرداند:
import { useLocation } from "react-router";
export default function CurrentLocation() {
const location = useLocation();
return (
<pre>
{JSON.stringify(location, null, 2)}
</pre>
);
}
مقادیر متداول:
location.pathname
location.search
location.hash
location.state
location.key
نمونه ثبت تغییر Route:
import { useEffect } from "react";
import { useLocation } from "react-router";
export default function RouteTracker() {
const location = useLocation();
useEffect(() => {
console.log("Route changed:", location.pathname);
}, [location.pathname]);
return null;
}
اطلاعات حساس کاربر یا محتوای خصوصی پیام را در ابزار Analytics و Logهای عمومی ثبت نکنید.
ارسال State هنگام Navigation
<Link
to="/playground"
state={{
from: "/models",
selectedModel: "YOUR_MODEL_ID",
}}
>
استفاده در Playground
</Link>
دریافت State:
import { useLocation } from "react-router";
export default function PlaygroundPage() {
const location = useLocation();
const selectedModel =
location.state?.selectedModel;
return (
<p>
مدل انتخابشده:
{selectedModel || "انتخاب نشده"}
</p>
);
}
Navigation State برای اطلاعات موقت مناسب است. برای دادهای که باید پس از Refresh باقی بماند یا قابل اشتراکگذاری باشد، URL، Backend یا State پایدار انتخاب مناسبتری است.
هرگز Secret را در Route State بهعنوان راهکار امنیتی قرار ندهید.
Query String و useSearchParams
URL زیر را در نظر بگیرید:
/models?category=text&sort=popular&page=2
بخش بعد از ? Query String است.
برای خواندن آن:
import { useSearchParams } from "react-router";
export default function ModelsPage() {
const [searchParams] = useSearchParams();
const category =
searchParams.get("category") || "all";
const sort =
searchParams.get("sort") || "popular";
const page = Number(
searchParams.get("page") || 1
);
return (
<div>
<p>دستهبندی: {category}</p>
<p>مرتبسازی: {sort}</p>
<p>صفحه: {page}</p>
</div>
);
}
برای تغییر Query Parameter:
import { useSearchParams } from "react-router";
export default function ModelFilters() {
const [searchParams, setSearchParams] =
useSearchParams();
function changeCategory(category) {
const nextParams =
new URLSearchParams(searchParams);
nextParams.set("category", category);
nextParams.set("page", "1");
setSearchParams(nextParams);
}
return (
<button
type="button"
onClick={() => changeCategory("coding")}
>
مدلهای برنامهنویسی
</button>
);
}
مقادیر Query String نیز ورودی کاربر هستند و باید اعتبارسنجی شوند.
چه دادهای را در URL قرار دهیم؟
URL برای Stateهایی مناسب است که:
- قابل اشتراکگذاریاند.
- باید Bookmark شوند.
- بعد از Refresh باقی میمانند.
- روی محتوای صفحه اثر میگذارند.
- برای Back و Forward مرورگر مهماند.
مثال:
/models?category=coding&sort=price
State محلی برای موارد موقت رابط کاربری مناسبتر است:
- باز یا بسته بودن Modal
- وضعیت Hover
- متن موقت یک Input
- Loading داخلی Component
- انتخاب موقتی که نباید در URL دیده شود
اطلاعات حساس مانند API Key، Password و Token نباید در URL قرار بگیرند؛ زیرا URL ممکن است در History، Log، Analytics و Referrer ثبت شود.
ساخت داشبورد هوش مصنوعی چندصفحهای
ساختار پروژه:
src/
├── components/
│ ├── Header.jsx
│ └── Sidebar.jsx
├── layouts/
│ ├── AppLayout.jsx
│ └── DashboardLayout.jsx
├── pages/
│ ├── HomePage.jsx
│ ├── PlaygroundPage.jsx
│ ├── ModelsPage.jsx
│ ├── HistoryPage.jsx
│ ├── ConversationPage.jsx
│ ├── SettingsPage.jsx
│ └── NotFoundPage.jsx
├── services/
│ └── chatService.js
├── App.jsx
├── main.jsx
└── index.css
ساخت AppLayout
فایل src/layouts/AppLayout.jsx:
import { Link, Outlet } from "react-router";
export default function AppLayout() {
return (
<div className="app-shell">
<header className="topbar">
<Link
className="brand"
to="/"
>
داشبورد هوش مصنوعی
</Link>
<a
href="https://darvareh.ir"
target="_blank"
rel="noreferrer"
>
درواره
</a>
</header>
<Outlet />
</div>
);
}
ساخت DashboardLayout
فایل src/layouts/DashboardLayout.jsx:
import { NavLink, Outlet } from "react-router";
const navigationItems = [
{
to: "/dashboard",
label: "نمای کلی",
end: true,
},
{
to: "/dashboard/playground",
label: "Playground",
},
{
to: "/dashboard/models",
label: "مدلها",
},
{
to: "/dashboard/history",
label: "تاریخچه",
},
{
to: "/dashboard/settings",
label: "تنظیمات",
},
];
export default function DashboardLayout() {
return (
<div className="dashboard-layout">
<aside className="sidebar">
<nav>
{navigationItems.map((item) => (
<NavLink
key={item.to}
to={item.to}
end={item.end}
className={({ isActive }) =>
isActive
? "sidebar-link active"
: "sidebar-link"
}
>
{item.label}
</NavLink>
))}
</nav>
</aside>
<main className="dashboard-content">
<Outlet />
</main>
</div>
);
}
تعریف Routeهای داشبورد
فایل src/App.jsx:
import { Route, Routes } from "react-router";
import AppLayout from "./layouts/AppLayout";
import DashboardLayout from "./layouts/DashboardLayout";
import HomePage from "./pages/HomePage";
import DashboardHomePage from "./pages/DashboardHomePage";
import PlaygroundPage from "./pages/PlaygroundPage";
import ModelsPage from "./pages/ModelsPage";
import HistoryPage from "./pages/HistoryPage";
import ConversationPage from "./pages/ConversationPage";
import SettingsPage from "./pages/SettingsPage";
import NotFoundPage from "./pages/NotFoundPage";
export default function App() {
return (
<Routes>
<Route element={<AppLayout />}>
<Route
index
element={<HomePage />}
/>
<Route
path="dashboard"
element={<DashboardLayout />}
>
<Route
index
element={<DashboardHomePage />}
/>
<Route
path="playground"
element={<PlaygroundPage />}
/>
<Route
path="models"
element={<ModelsPage />}
/>
<Route
path="history"
element={<HistoryPage />}
/>
<Route
path="conversations/:conversationId"
element={<ConversationPage />}
/>
<Route
path="settings"
element={<SettingsPage />}
/>
</Route>
<Route
path="*"
element={<NotFoundPage />}
/>
</Route>
</Routes>
);
}
Routeهای نهایی:
/
/dashboard
/dashboard/playground
/dashboard/models
/dashboard/history
/dashboard/conversations/:conversationId
/dashboard/settings
ساخت صفحه Models با فیلتر URL
فایل src/pages/ModelsPage.jsx:
import { useSearchParams } from "react-router";
const categories = [
{
value: "all",
label: "همه مدلها",
},
{
value: "text",
label: "متنی",
},
{
value: "coding",
label: "برنامهنویسی",
},
{
value: "image",
label: "تصویری",
},
];
export default function ModelsPage() {
const [searchParams, setSearchParams] =
useSearchParams();
const selectedCategory =
searchParams.get("category") || "all";
function handleCategoryChange(event) {
const nextCategory = event.target.value;
if (nextCategory === "all") {
setSearchParams({});
return;
}
setSearchParams({
category: nextCategory,
});
}
return (
<section>
<div className="page-heading">
<div>
<p>مدیریت مدلها</p>
<h1>مدلهای هوش مصنوعی</h1>
</div>
<a
href="https://darvareh.ir/models"
target="_blank"
rel="noreferrer"
>
فهرست مدلها و قیمتها
</a>
</div>
<label htmlFor="model-category">
دستهبندی
</label>
<select
id="model-category"
value={selectedCategory}
onChange={handleCategoryChange}
>
{categories.map((category) => (
<option
key={category.value}
value={category.value}
>
{category.label}
</option>
))}
</select>
<p>
دسته انتخابشده:
{" "}
{selectedCategory}
</p>
</section>
);
}
با انتخاب دسته برنامهنویسی، URL تغییر میکند:
/dashboard/models?category=coding
اکنون این فیلتر قابل Bookmark و اشتراکگذاری است.
ساخت صفحه تاریخچه
فایل src/pages/HistoryPage.jsx:
import { Link } from "react-router";
const conversations = [
{
id: 101,
title: "توضیح React Router",
},
{
id: 102,
title: "ساخت API با Node.js",
},
{
id: 103,
title: "بررسی کد JavaScript",
},
];
export default function HistoryPage() {
return (
<section>
<h1>تاریخچه مکالمات</h1>
<ul className="conversation-list">
{conversations.map((conversation) => (
<li key={conversation.id}>
<Link
to={
`/dashboard/conversations/${conversation.id}`
}
>
{conversation.title}
</Link>
</li>
))}
</ul>
</section>
);
}
ساخت صفحه جزئیات مکالمه
فایل src/pages/ConversationPage.jsx:
import {
Link,
useParams,
} from "react-router";
export default function ConversationPage() {
const { conversationId } = useParams();
const numericId = Number(conversationId);
if (
!Number.isInteger(numericId) ||
numericId <= 0
) {
return (
<section>
<h1>شناسه نامعتبر است</h1>
<Link to="/dashboard/history">
بازگشت به تاریخچه
</Link>
</section>
);
}
return (
<section>
<p>جزئیات مکالمه</p>
<h1>مکالمه شماره {numericId}</h1>
<Link to="/dashboard/history">
بازگشت به تاریخچه
</Link>
</section>
);
}
در پروژه واقعی باید داده مکالمه از Backend دریافت شود. Backend باید بررسی کند مکالمه متعلق به کاربر فعلی است.
ساخت Playground هوش مصنوعی
فایل src/services/chatService.js:
export async function sendMessage(
message,
signal
) {
const response = await fetch("/api/chat", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
message,
}),
signal,
});
const data =
await response.json().catch(() => null);
if (!response.ok) {
throw new Error(
data?.error ||
"دریافت پاسخ با خطا روبهرو شد."
);
}
if (typeof data?.answer !== "string") {
throw new Error(
"ساختار پاسخ قابل پردازش نیست."
);
}
return data.answer;
}
فایل src/pages/PlaygroundPage.jsx:
import {
useRef,
useState,
} from "react";
import { sendMessage } from "../services/chatService";
export default function PlaygroundPage() {
const [message, setMessage] = useState("");
const [answer, setAnswer] = useState("");
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
const controllerRef = useRef(null);
async function handleSubmit(event) {
event.preventDefault();
const cleanMessage = message.trim();
if (!cleanMessage || loading) {
return;
}
controllerRef.current?.abort();
controllerRef.current =
new AbortController();
setLoading(true);
setError("");
setAnswer("");
try {
const result = await sendMessage(
cleanMessage,
controllerRef.current.signal
);
setAnswer(result);
} catch (requestError) {
if (requestError.name !== "AbortError") {
setError(
requestError instanceof Error
? requestError.message
: "خطای پیشبینینشدهای رخ داد."
);
}
} finally {
setLoading(false);
controllerRef.current = null;
}
}
function handleCancel() {
controllerRef.current?.abort();
}
return (
<section>
<p>محیط آزمایش</p>
<h1>Playground هوش مصنوعی</h1>
<form
className="playground-form"
onSubmit={handleSubmit}
>
<label htmlFor="message">
پیام
</label>
<textarea
id="message"
value={message}
onChange={(event) =>
setMessage(event.target.value)
}
rows="8"
disabled={loading}
placeholder="پیام خود را وارد کنید."
/>
<div className="form-actions">
{loading && (
<button
type="button"
onClick={handleCancel}
>
توقف
</button>
)}
<button
type="submit"
disabled={
loading || !message.trim()
}
>
{loading
? "در حال دریافت پاسخ..."
: "ارسال"}
</button>
</div>
</form>
{error && (
<p role="alert">
{error}
</p>
)}
{answer && (
<article>
<h2>پاسخ</h2>
<p>{answer}</p>
</article>
)}
</section>
);
}
چرا API Key نباید در React قرار بگیرد؟
کد React به مرورگر کاربر ارسال میشود. قرار دادن API Key در Source Code، فایل JavaScript یا متغیر محیطی قابلدسترسی Frontend باعث میشود کاربر بتواند آن را مشاهده کند.
این روش ناامن است:
fetch(
"https://api.darvareh.ir/v1/chat/completions",
{
headers: {
Authorization:
"Bearer YOUR_DARVAREH_API_KEY",
},
}
);
این روش نیز امن نیست:
VITE_DARVAREH_API_KEY=YOUR_DARVAREH_API_KEY
معماری مناسب:
React Frontend
|
| /api/chat
v
Backend برنامه شما
|
| API Key خصوصی
v
API هوش مصنوعی درواره
ساخت Backend امن
در ریشه پروژه:
mkdir server
cd server
npm init -y
npm install express dotenv
فایل server/package.json:
{
"name": "react-router-ai-backend",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "node --watch server.js",
"start": "node server.js"
}
}
فایل server/.env:
DARVAREH_API_KEY=YOUR_DARVAREH_API_KEY
DARVAREH_MODEL_ID=YOUR_MODEL_ID
PORT=3000
شناسه مدل و اطلاعات بهروز قیمتها را از صفحه مدلهای درواره دریافت کنید.
فایل .env را وارد Git نکنید:
server/.env
node_modules
dist
پیادهسازی Endpoint
فایل server/server.js:
import "dotenv/config";
import express from "express";
const app = express();
const port = Number(
process.env.PORT || 3000
);
app.disable("x-powered-by");
app.use(
express.json({
limit: "32kb",
})
);
app.get(
"/api/health",
(request, response) => {
response.json({
status: "ok",
});
}
);
app.post(
"/api/chat",
async (request, response) => {
const message =
typeof request.body?.message === "string"
? request.body.message.trim()
: "";
if (!message) {
return response.status(400).json({
error: "متن پیام الزامی است.",
});
}
if (message.length > 4000) {
return response.status(400).json({
error:
"طول پیام بیشتر از مقدار مجاز است.",
});
}
if (
!process.env.DARVAREH_API_KEY ||
!process.env.DARVAREH_MODEL_ID
) {
console.error(
"Darvareh configuration is missing."
);
return response.status(500).json({
error:
"تنظیمات سرویس کامل نیست.",
});
}
try {
const upstreamResponse = await fetch(
"https://api.darvareh.ir/v1/chat/completions",
{
method: "POST",
headers: {
Authorization:
`Bearer ${process.env.DARVAREH_API_KEY}`,
"Content-Type":
"application/json",
},
body: JSON.stringify({
model:
process.env.DARVAREH_MODEL_ID,
messages: [
{
role: "system",
content:
"شما یک دستیار فارسی دقیق و کاربردی هستید.",
},
{
role: "user",
content: message,
},
],
temperature: 0.4,
}),
}
);
const data =
await upstreamResponse
.json()
.catch(() => null);
if (!upstreamResponse.ok) {
console.error(
"Darvareh API error:",
{
status:
upstreamResponse.status,
}
);
return response.status(502).json({
error:
"سرویس هوش مصنوعی پاسخ معتبری برنگرداند.",
});
}
const answer =
data?.choices?.[0]?.message?.content;
if (
typeof answer !== "string" ||
!answer.trim()
) {
return response.status(502).json({
error:
"ساختار پاسخ قابل پردازش نبود.",
});
}
return response.json({
answer: answer.trim(),
});
} catch (error) {
console.error(
"Unexpected error:",
error
);
return response.status(500).json({
error:
"ارتباط با سرویس برقرار نشد.",
});
}
}
);
app.listen(port, () => {
console.log(
`Backend is running on port ${port}`
);
});
تنظیم Proxy در Vite
فایل vite.config.js:
import {
defineConfig,
} from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [
react(),
],
server: {
proxy: {
"/api": {
target:
"http://localhost:3000",
changeOrigin: true,
},
},
},
});
اکنون درخواست /api/chat در محیط توسعه به Backend روی پورت 3000 هدایت میشود.
Proxy مربوط به Development Server است و در Build نهایی فعال نیست. برای Production باید مسیر Backend را در Web Server یا سرویس Hosting تنظیم کنید.
Protected Route چیست؟
Protected Route رابط کاربری یک Route را فقط در صورت وجود وضعیت احراز هویت نمایش میدهد.
مثال ساده:
import {
Navigate,
Outlet,
useLocation,
} from "react-router";
export default function ProtectedRoute({
authenticated,
}) {
const location = useLocation();
if (!authenticated) {
return (
<Navigate
to="/login"
replace
state={{
from: location.pathname,
}}
/>
);
}
return <Outlet />;
}
استفاده:
<Routes>
<Route
path="/login"
element={<LoginPage />}
/>
<Route
element={
<ProtectedRoute
authenticated={authenticated}
/>
}
>
<Route
path="/dashboard"
element={<DashboardLayout />}
>
<Route
index
element={<DashboardHomePage />}
/>
</Route>
</Route>
</Routes>
پس از Login:
const location = useLocation();
const navigate = useNavigate();
const destination =
location.state?.from || "/dashboard";
navigate(destination, {
replace: true,
});
Protected Route مرز امنیتی نیست
کاربر میتواند JavaScript Frontend را تغییر دهد، Request دستی ارسال کند یا مستقیم Endpoint را فراخوانی کند.
بنابراین Backend باید برای هر Request بررسی کند:
- کاربر احراز هویت شده است؟
- حساب فعال است؟
- کاربر مالک Resource است؟
- نقش مناسب دارد؟
- اجازه استفاده از مدل انتخابی را دارد؟
- محدودیت مصرف رعایت شده است؟
Protected Route فقط تجربه کاربری و Navigation را کنترل میکند.
Role-based Route
برای نمایش Route براساس نقش:
function RoleRoute({
user,
allowedRoles,
}) {
if (!user) {
return (
<Navigate
to="/login"
replace
/>
);
}
if (
!allowedRoles.includes(user.role)
) {
return (
<Navigate
to="/forbidden"
replace
/>
);
}
return <Outlet />;
}
استفاده:
<Route
element={
<RoleRoute
user={user}
allowedRoles={["admin"]}
/>
}
>
<Route
path="/admin"
element={<AdminPage />}
/>
</Route>
Backend همچنان باید Role را مستقل بررسی کند.
Lazy Loading صفحهها
اگر تمام صفحات در Bundle اولیه قرار بگیرند، حجم JavaScript اولیه افزایش پیدا میکند. میتوان صفحههای سنگین را با lazy بارگذاری کرد.
import {
lazy,
Suspense,
} from "react";
import {
Route,
Routes,
} from "react-router";
const PlaygroundPage = lazy(
() =>
import(
"./pages/PlaygroundPage"
)
);
const ModelsPage = lazy(
() =>
import("./pages/ModelsPage")
);
export default function App() {
return (
<Suspense
fallback={
<p>در حال بارگذاری صفحه...</p>
}
>
<Routes>
<Route
path="/playground"
element={<PlaygroundPage />}
/>
<Route
path="/models"
element={<ModelsPage />}
/>
</Routes>
</Suspense>
);
}
Lazy Loading برای صفحههایی مناسب است که:
- در ورود اولیه لازم نیستند.
- کتابخانههای سنگین دارند.
- تعداد کاربران کمتری به آنها مراجعه میکنند.
- شامل Chart یا Editor بزرگ هستند.
هر Component کوچک را بدون اندازهگیری Lazy نکنید. تعداد زیاد Chunk نیز میتواند مدیریت و بارگذاری را پیچیده کند.
ساخت Error Boundary در Data Router
در Data Mode میتوان Error Element مربوط به Route تعریف کرد.
نمونه Router:
import {
createBrowserRouter,
RouterProvider,
} from "react-router";
import AppLayout from "./layouts/AppLayout";
import HomePage from "./pages/HomePage";
import ModelsPage from "./pages/ModelsPage";
import RouteErrorPage from "./pages/RouteErrorPage";
const router = createBrowserRouter([
{
path: "/",
element: <AppLayout />,
errorElement: <RouteErrorPage />,
children: [
{
index: true,
element: <HomePage />,
},
{
path: "models",
element: <ModelsPage />,
},
],
},
]);
export default function App() {
return (
<RouterProvider
router={router}
/>
);
}
در این ساختار دیگر نباید BrowserRouter جداگانه دور RouterProvider قرار دهید.

Loader در Data Router
Loader میتواند پیش از نمایش Route داده موردنیاز را دریافت کند:
import {
createBrowserRouter,
RouterProvider,
} from "react-router";
async function modelsLoader() {
const response = await fetch(
"/api/models"
);
if (!response.ok) {
throw new Response(
"Models could not be loaded",
{
status: response.status,
}
);
}
return response.json();
}
تعریف Route:
const router = createBrowserRouter([
{
path: "/models",
loader: modelsLoader,
element: <ModelsPage />,
},
]);
استفاده از داده:
import {
useLoaderData,
} from "react-router";
export default function ModelsPage() {
const models = useLoaderData();
return (
<ul>
{models.map((model) => (
<li key={model.id}>
{model.name}
</li>
))}
</ul>
);
}
Data Router امکانات گستردهتری دارد، اما برای پروژه کوچک ممکن است Declarative Mode کافی باشد.
BrowserRouter یا HashRouter؟
BrowserRouter
URL تمیز تولید میکند:
https://example.com/dashboard/models
اما Web Server باید تمام Routeهای Frontend را به index.html Rewrite کند.
HashRouter
Route بعد از # قرار میگیرد:
https://example.com/#/dashboard/models
بخش Hash معمولاً به سرور ارسال نمیشود؛ بنابراین در Hostingهای بسیار ساده احتمال خطای Refresh کمتر است.
مثال:
import {
HashRouter,
} from "react-router";
<HashRouter>
<App />
</HashRouter>
برای بیشتر پروژههای حرفهای با کنترل مناسب روی Hosting، BrowserRouter و تنظیم Rewrite انتخاب رایجتری است. HashRouter میتواند برای Static Hosting محدود یا ابزارهای خاص کاربرد داشته باشد.
علت خطای 404 پس از Refresh
فرض کنید در برنامه به این Route رفتهاید:
/dashboard/models
Navigation داخلی درست کار میکند؛ اما با Refresh، مرورگر مستقیماً از سرور همین Path را درخواست میکند:
GET /dashboard/models
اگر سرور فقط فایل /index.html را بشناسد، خطای 404 میدهد.
راهحل این است که سرور برای Routeهای Frontend، index.html را برگرداند تا React Router URL را پردازش کند.
تنظیم Nginx برای React Router
نمونه ساده:
server {
listen 80;
server_name example.com;
root /var/www/app/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
در این تنظیم:
- فایل موجود مستقیماً ارائه میشود.
- اگر فایل یا پوشه وجود نداشته باشد،
index.htmlبرگردانده میشود. - درخواستهای
/api/به Backend هدایت میشوند.
تنظیم Production باید براساس زیرساخت واقعی، HTTPS، Cache و نیازهای Backend بازبینی شود.
ارائه Build React با Express
بعد از Build:
npm run build
میتوانید فایلهای dist را با Express ارائه کنید:
import path from "node:path";
import {
fileURLToPath,
} from "node:url";
const currentFile =
fileURLToPath(import.meta.url);
const currentDirectory =
path.dirname(currentFile);
const frontendDirectory =
path.resolve(
currentDirectory,
"../dist"
);
app.use(
express.static(
frontendDirectory
)
);
بعد از تعریف Routeهای /api و قبل از app.listen، Fallback مربوط به SPA را اضافه کنید. Syntax دقیق Catch-all میتواند با نسخه Express تفاوت داشته باشد؛ بنابراین آن را با نسخه نصبشده بررسی کنید.
هدف Fallback این است که Routeهایی مانند زیر index.html را دریافت کنند:
/dashboard
/dashboard/models
/dashboard/conversations/42
تنظیم basename
اگر برنامه در زیرمسیر منتشر میشود:
https://example.com/ai-dashboard/
میتوانید basename تعریف کنید:
<BrowserRouter
basename="/ai-dashboard"
>
<App />
</BrowserRouter>
حالا Route داخلی /models در این آدرس نمایش داده میشود:
https://example.com/ai-dashboard/models
تنظیم base در Vite و basename در Router باید با مسیر استقرار هماهنگ باشند.
بازگرداندن Scroll به بالای صفحه
در SPA، Navigation همیشه Scroll را مانند بارگذاری سنتی صفحه Reset نمیکند. یک Component ساده بسازید:
import {
useEffect,
} from "react";
import {
useLocation,
} from "react-router";
export default function ScrollToTop() {
const { pathname } =
useLocation();
useEffect(() => {
window.scrollTo({
top: 0,
left: 0,
behavior: "instant",
});
}, [pathname]);
return null;
}
استفاده:
<BrowserRouter>
<ScrollToTop />
<App />
</BrowserRouter>
برای سناریوهای پیچیدهتر مانند حفظ Scroll در صفحات فهرستی باید رفتار دقیقتری طراحی کنید.
تنظیم عنوان صفحه براساس Route
برای هر Page میتوانید عنوان Document را تغییر دهید:
import {
useEffect,
} from "react";
export default function ModelsPage() {
useEffect(() => {
document.title =
"مدلهای هوش مصنوعی | داشبورد";
}, []);
return <h1>مدلها</h1>;
}
یک Hook قابل استفاده مجدد:
import {
useEffect,
} from "react";
export function usePageTitle(title) {
useEffect(() => {
const previousTitle =
document.title;
document.title = title;
return () => {
document.title =
previousTitle;
};
}, [title]);
}
استفاده:
usePageTitle(
"Playground هوش مصنوعی"
);
برای سایتهای محتوایی و SEOمحور، فقط تغییر Client-side عنوان کافی نیست و باید Rendering، HTML اولیه، Meta Tagها و دسترسی خزندهها نیز بررسی شود.
Route و SEO
React Router ابزار Routing است، نه یک راهکار کامل SEO.
برای داشبوردهای خصوصی و ابزارهای تعاملی، Client-side Routing معمولاً مناسب است. اما برای صفحات عمومی وابسته به ترافیک ارگانیک باید موارد زیر بررسی شوند:
- HTML اولیه
- Title و Meta Description
- Canonical URL
- Structured Data
- Server-side Rendering
- Static Generation
- Status Code صحیح
- Sitemap
- لینک داخلی واقعی
- سرعت بارگذاری
- محتوای قابل مشاهده برای خزنده
صرف داشتن URLهای جداگانه تضمین نمیکند همه صفحات بهدرستی ایندکس شوند.
تست Routeها با MemoryRouter
برای تست Component وابسته به Router میتوان از MemoryRouter استفاده کرد:
import {
MemoryRouter,
} from "react-router";
render(
<MemoryRouter
initialEntries={[
"/dashboard/models?category=coding",
]}
>
<ModelsPage />
</MemoryRouter>
);
MemoryRouter History را در حافظه نگه میدارد و برای تست یا محیطهای غیرمرورگری مناسب است.
نمونه تست ساده:
import {
render,
screen,
} from "@testing-library/react";
import {
MemoryRouter,
} from "react-router";
test(
"models page reads category from URL",
() => {
render(
<MemoryRouter
initialEntries={[
"/models?category=coding",
]}
>
<ModelsPage />
</MemoryRouter>
);
expect(
screen.getByText(
/coding/i
)
).toBeInTheDocument();
}
);
تست Navigation
function TestRoutes() {
return (
<Routes>
<Route
path="/"
element={
<Link to="/models">
مدلها
</Link>
}
/>
<Route
path="/models"
element={
<h1>مدلهای هوش مصنوعی</h1>
}
/>
</Routes>
);
}
تست:
render(
<MemoryRouter
initialEntries={["/"]}
>
<TestRoutes />
</MemoryRouter>
);
await user.click(
screen.getByRole(
"link",
{
name: "مدلها",
}
)
);
expect(
screen.getByRole(
"heading",
{
name:
"مدلهای هوش مصنوعی",
}
)
).toBeInTheDocument();
اشتباهات رایج React Router
استفاده از Router تودرتو
این ساختار معمولاً اشتباه است:
<BrowserRouter>
<App>
<BrowserRouter>
<Dashboard />
</BrowserRouter>
</App>
</BrowserRouter>
در بیشتر برنامهها یک Router در ریشه کافی است.
فراموش کردن Outlet
Route فرزند تعریف شده، اما نمایش داده نمیشود:
<Route
path="/dashboard"
element={<DashboardLayout />}
>
<Route
path="settings"
element={<Settings />}
/>
</Route>
والد باید Outlet داشته باشد:
function DashboardLayout() {
return (
<main>
<h1>داشبورد</h1>
<Outlet />
</main>
);
}
استفاده از href برای لینک داخلی
بهجای:
<a href="/settings">
تنظیمات
</a>
استفاده کنید:
<Link to="/settings">
تنظیمات
</Link>
استفاده از مسیر مطلق اشتباه در Route فرزند
در Nested Routes معمولاً Path فرزند نسبی نوشته میشود:
<Route
path="dashboard"
element={<DashboardLayout />}
>
<Route
path="settings"
element={<Settings />}
/>
</Route>
Route نهایی:
/dashboard/settings
استفاده از useNavigate خارج Router
Hookهای React Router فقط داخل Context آن قابلاستفادهاند.
اعتماد به Protected Route برای امنیت
Backend باید Access Control را مستقل اجرا کند.
قرار دادن Secret در URL
این کار خطرناک است:
/playground?apiKey=secret
Secret ممکن است در History، Log و Analytics ثبت شود.
نداشتن Route مربوط به 404
<Route
path="*"
element={<NotFoundPage />}
/>
نداشتن Rewrite روی سرور
Navigation داخلی ممکن است کار کند، اما Refresh یک Route با 404 مواجه شود.
ذخیره تمام State در URL
URL برای State قابل اشتراکگذاری مناسب است، نه تمام جزئیات موقت UI.
استفاده بیدلیل از useNavigate
برای لینک قابلکلیک از Link یا NavLink استفاده کنید.
خطاهای رایج و راهحل آنها
useNavigate may be used only in the context of a Router
Component خارج BrowserRouter است.
در main.jsx:
<BrowserRouter>
<App />
</BrowserRouter>
No routes matched location
هیچ Route مطابق با URL وجود ندارد. Pathها و Route مربوط به * را بررسی کنید:
<Route
path="*"
element={<NotFoundPage />}
/>
صفحه Nested خالی است
وجود Outlet در Layout والد را بررسی کنید.
لینک فعال اشتباه است
برای Route ریشه از end استفاده کنید:
<NavLink
to="/"
end
>
خانه
</NavLink>
Refresh در Production خطای 404 میدهد
Web Server باید Fallback به index.html داشته باشد.
Query Parameter بهروزرسانی نمیشود
بهجای تغییر مستقیم Object موجود، یک URLSearchParams جدید بسازید:
const nextParams =
new URLSearchParams(
searchParams
);
nextParams.set(
"category",
"coding"
);
setSearchParams(nextParams);
Route Parameter مقدار undefined دارد
نام Parameter در Route و useParams باید یکسان باشد:
<Route
path="/models/:modelId"
element={<ModelPage />}
/>
const { modelId } =
useParams();
Importها با آموزش اینترنتی متفاوتاند
نسخه Package را بررسی کنید:
npm list react-router react-router-dom
سپس مستندات همان نسخه را بخوانید. آموزشهای قدیمی ممکن است از API و Importهای نسخه متفاوت استفاده کنند.
بهترین روش طراحی Routeها
Routeها باید ساختار محصول را نشان دهند:
/dashboard
/dashboard/models
/dashboard/playground
/dashboard/history
/dashboard/settings
بهتر است:
- URLها کوتاه و قابلفهم باشند.
- نامها پایدار باشند.
- ساختار بیش از حد عمیق نشود.
- IDهای حساس یا Secret وارد URL نشوند.
- Routeهای مرتبط Layout مشترک داشته باشند.
- صفحه 404 طراحی شود.
- Redirect مسیرهای قدیمی مشخص باشد.
- دسترسی Backend مستقل بررسی شود.
سازماندهی Routeها در پروژه بزرگ
در پروژه کوچک میتوان Routeها را در App.jsx نگه داشت. در پروژه بزرگ بهتر است فایل جداگانه داشته باشید:
src/
├── router/
│ ├── AppRoutes.jsx
│ ├── ProtectedRoute.jsx
│ └── RoleRoute.jsx
فایل AppRoutes.jsx:
import {
Route,
Routes,
} from "react-router";
export default function AppRoutes() {
return (
<Routes>
{/* Route definitions */}
</Routes>
);
}
در App.jsx:
import AppRoutes from "./router/AppRoutes";
export default function App() {
return <AppRoutes />;
}
از تقسیم بیش از حد Routeها در پروژه کوچک خودداری کنید. ساختار باید پیچیدگی واقعی محصول را منعکس کند.
چکلیست انتشار پروژه React Router
پیش از انتشار بررسی کنید:
- Router فقط یکبار در ریشه برنامه قرار گرفته است.
- همه Routeهای اصلی تعریف شدهاند.
- Nested Routeها
Outletدارند. - صفحه 404 وجود دارد.
- Navigation داخلی با
LinkیاNavLinkانجام میشود. - لینک خارجی از Anchor مناسب استفاده میکند.
- Dynamic Parameterها اعتبارسنجی میشوند.
- Query Parameterها اعتبارسنجی میشوند.
- Secret داخل URL قرار نمیگیرد.
- Protected Route فقط برای UX در نظر گرفته شده است.
- Backend دسترسی واقعی را بررسی میکند.
- API Key فقط در Backend نگهداری میشود.
- Routeهای سنگین در صورت نیاز Lazy Load میشوند.
- Loading و Error State طراحی شده است.
- Refresh مستقیم Routeها آزمایش شده است.
- Rewrite سرور تنظیم شده است.
- مقدار
basenameبا مسیر انتشار سازگار است. - Title صفحهها تنظیم شده است.
- Navigation با Keyboard قابلاستفاده است.
- Routeها در موبایل آزمایش شدهاند.
- تست Routeهای مهم نوشته شده است.
- Lint، Test و Build بدون خطا اجرا میشوند.
پرسشهای متداول
React Router چیست؟
React Router کتابخانهای برای هماهنگ کردن URL مرورگر با Componentهای برنامه React است. با آن میتوان صفحهها، مسیرهای تودرتو، مسیرهای پویا و Navigation سمت Client ساخت.
آیا React Router بخشی از React است؟
خیر. React Router یک کتابخانه جداگانه است و باید به پروژه اضافه شود.
react-router-dom چیست؟
react-router-dom Package شناختهشده نسخههای قبلی و بسیاری از پروژههای موجود برای Routing در مرورگر است. در مستندات نسخه فعلی، Declarative Mode با Package react-router آموزش داده میشود. همیشه نسخه پروژه خود را بررسی کنید.
چگونه React Router را نصب کنیم؟
براساس مستندات فعلی:
npm install react-router
برای پروژه موجود ابتدا نسخه و Package فعلی را بررسی کنید.
BrowserRouter چیست؟
BrowserRouter برنامه را به History API مرورگر متصل میکند و URLهای معمولی مانند /dashboard/models میسازد.
Route چیست؟
Route یک Path را به Element رابط کاربری متصل میکند:
<Route
path="/models"
element={<ModelsPage />}
/>
Outlet چیست؟
Outlet محل نمایش Route فرزند در Layout والد است.
useParams چه کاری انجام میدهد؟
Dynamic Parameterهای URL را برمیگرداند:
const { conversationId } =
useParams();
useNavigate چه کاربردی دارد؟
برای Navigation برنامهنویسیشده پس از رویدادهایی مانند Login، ثبت فرم یا ساخت Resource استفاده میشود.
useSearchParams چیست؟
برای خواندن و تغییر Query String URL استفاده میشود:
const [
searchParams,
setSearchParams,
] = useSearchParams();
چرا پس از Refresh خطای 404 میگیرم؟
زیرا سرور Route Frontend را نمیشناسد. باید Web Server طوری تنظیم شود که Routeهای SPA را به index.html Rewrite کند.
Protected Route برای امنیت کافی است؟
خیر. Protected Route فقط نمایش رابط کاربری را کنترل میکند. Backend باید احراز هویت و مجوز دسترسی را برای هر Request بررسی کند.
آیا React Router برای SEO مناسب است؟
React Router فقط Routing را فراهم میکند. SEO به Rendering، HTML اولیه، Meta Tagها، Status Code، Sitemap، محتوای صفحه و دسترسی خزندهها نیز وابسته است.
چگونه صفحه 404 بسازیم؟
<Route
path="*"
element={<NotFoundPage />}
/>
چگونه API هوش مصنوعی را به Route Playground متصل کنیم؟
Frontend باید Request را به Backend شما بفرستد و Backend با API Key خصوصی به API هوش مصنوعی متصل شود:
React -> Backend -> API درواره
شناسه مدل و قیمت را از کجا ببینیم؟
صفحه مدلهای درواره اطلاعات بهروز مدلها، شناسه قابلاستفاده و قیمت آنها را نمایش میدهد.
جمعبندی
React Router ابزار اصلی ساخت Routing در بسیاری از اپلیکیشنهای React است. این کتابخانه URL را به Componentهای رابط کاربری متصل میکند و امکاناتی مانند Navigation بدون Reload کامل، Routeهای تودرتو، Layout مشترک، Dynamic Parameter و Query String را فراهم میکند.
مهمترین نکات این آموزش:
BrowserRouterبرنامه را به History مرورگر متصل میکند.RoutesوRouteمسیرها را تعریف میکنند.- برای Navigation داخلی از
LinkوNavLinkاستفاده میشود. Outletمحل نمایش Route فرزند است.useParamsDynamic Parameterها را میخواند.useSearchParamsQuery String را مدیریت میکند.useNavigateبرای Navigation پس از عملیات مناسب است.- Protected Route یک مرز امنیتی واقعی نیست.
- Backend باید Access Control را مستقل اجرا کند.
- API Key هوش مصنوعی نباید در React قرار بگیرد.
- برای Refresh مستقیم Routeها باید Rewrite سرور تنظیم شود.
- صفحههای سنگین را میتوان بهصورت Lazy بارگذاری کرد.
- نسخه React Router باید با مستندات مورداستفاده تطبیق داده شود.
با استفاده از React Router میتوانید یک داشبورد چندصفحهای برای Playground، مدلها، تاریخچه درخواستها و تنظیمات بسازید و Backend آن را به API هوش مصنوعی درواره متصل کنید.
برای مشاهده مدلها، شناسه مدل و اطلاعات بهروز قیمت، به صفحه مدلهای درواره مراجعه کنید.
مقالات مرتبط
- React چیست؟ آموزش کامل ریاکت از صفر با پروژه عملی
- ساخت اپلیکیشن با هوش مصنوعی؛ راهنمای کامل و کاربردی
- ساخت چتبات هوش مصنوعی با Next.js، React و API درواره
- آموزش Node.js و Express برای اتصال به API هوش مصنوعی
- ساخت اپلیکیشن موبایل با React Native، Expo و API درواره
- اضافه کردن ChatGPT به سایت؛ راهنمای عملی
- آموزش اتصال API هوش مصنوعی به اپلیکیشن
- ساخت API هوش مصنوعی آماده Production
- HTTP چیست؟ آموزش Request، Response و Status Code
- آموزش کامل REST API و طراحی RESTful
منابع تکمیلی
- راهنمای نصب React Router
- راهنمای Routing و Nested Routes
- راهنمای Navigation در React Router
- راهنمای URL Values، Params و Search Params
- مستندات رسمی React Router
برای مطالعه شرایط استفاده و محدودیتهای مسئولیت، صفحه «سلب مسئولیت» را مشاهده کنید.