Root Layout، تنظیم lang="fa" و dir="rtl"

تا اینجا هر page.tsx فقط محتوای صفحه‌ی خودش را تعریف می‌کرد. اما تگ‌های html و body، زبان صفحه و جهت متن کجا تعیین می‌شوند؟ جواب، فایل app/layout.tsx است که به آن Root Layout می‌گوییم. در این درس این فایل را می‌شناسیم و پروژه‌ی DevNotes را برای زبان فارسی و چیدمان راست‌چین آماده می‌کنیم.

پیش‌نیاز

  • درس «ساخت صفحات با page.tsx»
  • آشنایی با ویژگی‌های lang و dir در HTML

Layout چیست؟

Layout یک کامپوننت است که ساختار مشترک چند صفحه را نگه می‌دارد و صفحه‌ی درونی را به‌عنوان children می‌گیرد. Next.js هنگام نمایش هر مسیر، ابتدا Layout را رندر می‌کند و صفحه را درون آن قرار می‌دهد. وقتی بین صفحاتی که Layout مشترک دارند جابه‌جا می‌شوید، Layout دوباره از اول ساخته نمی‌شود و فقط بخش صفحه عوض می‌شود. به همین دلیل بخش‌های تکراری مثل هدر و فوتر بهترین کاندیدا برای Layout هستند. Layoutهای تودرتو را در درس بعد، «Nested Layoutها»، می‌بینیم.

Root Layout چیست؟

Root Layout بالاترین Layout در پوشه‌ی app است. قواعد مهم آن:

  • وجود آن در App Router الزامی است و برای تمام مسیرهای برنامه اعمال می‌شود.
  • باید تگ‌های <html> و <body> را خودش تعریف کند. صفحات و Layoutهای دیگر این تگ‌ها را تعریف نمی‌کنند.
  • باید children را رندر کند؛ همان‌جا صفحه یا Layout درونی قرار می‌گیرد.
  • به‌صورت پیش‌فرض یک Server Component است.

نگاهی به Root Layout پروژه

فایل app/layout.tsx که create-next-app ساخته است چیزی شبیه این دارد. جزئیات دقیق، مثل فونت‌های وارد‌شده، ممکن است بسته به نسخه‌ی ابزار کمی فرق کند:

tsx
// app/layout.tsx (نمونه‌ی اولیه‌ی تولیدشده)
import type { Metadata } from "next";
import { Geist } from "next/font/google";
import "./globals.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={geistSans.variable}>{children}</body>
    </html>
  );
}

دو چیز در همین نمونه توجه ما را جلب می‌کند: lang="en" که زبان صفحه را انگلیسی اعلام می‌کند و نبود dir. فونت Geist فقط زیرمجموعه‌ی لاتین دارد و برای متن فارسی مناسب نیست؛ فونت فارسی را در درس «next/font و بارگذاری فونت فارسی» تنظیم می‌کنیم.

ویژگی‌های lang و dir

ویژگی مقدار برای DevNotes اثر
lang "fa" زبان محتوا را به مرورگر، صفحه‌خوان‌ها و موتورهای جست‌وجو اعلام می‌کند. بر تلفظ صفحه‌خوان‌ها، غلط‌یاب املایی و ترجمه‌ی خودکار مرورگر اثر می‌گذارد.
dir "rtl" جهت پایه‌ی متن و چیدمان را راست‌به‌چپ می‌کند.

این دو را روی تگ html می‌گذاریم تا برای کل برنامه اعمال شوند. lang مربوط به معنای زبان است و dir مربوط به جهت نمایش؛ هر دو لازم‌اند و یکی جای دیگری را نمی‌گیرد.

عملی: Root Layout فارسی DevNotes

فایل app/layout.tsx را با کد زیر جایگزین کنید:

tsx
// app/layout.tsx
import type { Metadata } from "next";
import type { ReactNode } from "react";
import "./globals.css";

export const metadata: Metadata = {
  title: "DevNotes",
  description: "وب‌سایتی برای انتشار و مدیریت نوشته‌های فنی",
};

export default function RootLayout({
  children,
}: Readonly<{ children: ReactNode }>) {
  return (
    <html lang="fa" dir="rtl">
      <body>{children}</body>
    </html>
  );
}

توضیح کد

  • import "./globals.css": استایل سراسری را وارد می‌کند. Root Layout بهترین جا برای این import است، چون برای همه‌ی صفحات اعمال می‌شود.
  • metadata: عنوان و توضیح پیش‌فرض سایت را تعریف می‌کند. فعلاً فقط مقدار فارسی گذاشته‌ایم؛ Metadata را در درس «Metadata ثابت و پویا» کامل بررسی می‌کنیم.
  • ReactNode: نوع هر چیزی که React می‌تواند رندر کند، از جمله صفحه‌ی درونی. آن را از react وارد کرده‌ایم تا به نوع سراسری React تکیه نکنیم.
  • Readonly<...>: فقط برای اینکه props تغییرناپذیر مشخص شود. حذف آن عملکرد را تغییر نمی‌دهد.
  • <html lang="fa" dir="rtl">: زبان را فارسی و جهت را راست‌به‌چپ می‌کند.
  • {children} داخل body: صفحه‌ی جاری در این نقطه رندر می‌شود.

پروژه‌ی ما فعلاً فونت Geist را حذف کرده است، پس متن با فونت پیش‌فرض سیستم نشان داده می‌شود. این موقتی است و بعداً فونت فارسی را اضافه می‌کنیم.

خروجی و رفتار مورد انتظار

فایل را ذخیره کنید و صفحه‌های /، /about و /posts را ببینید. باید این تغییرات را مشاهده کنید:

  • متن‌ها از سمت راست صفحه شروع می‌شوند.
  • عنوان تب مرورگر «DevNotes» است.
  • برای بررسی خروجی، در مرورگر روی صفحه کلیک راست و گزینه‌ی «View Page Source» را بزنید؛ تگ html باید به این شکل باشد:
html
<html lang="fa" dir="rtl">

اگر تغییر را بلافاصله ندیدید، یک‌بار صفحه را ریفرش کنید. تغییر در Root Layout گاهی به Fast Refresh کامل نیاز دارد.

اثر dir="rtl" بر چیدمان

dir="rtl" فقط جهت متن را عوض نمی‌کند، بلکه چیدمان را هم تحت‌تأثیر قرار می‌دهد:

  • در Flexbox و Grid، ترتیب پیش‌فرض آیتم‌ها از راست به چپ می‌شود.
  • مقدارهایی مثل text-align: start به راست اشاره می‌کنند.
  • ویژگی‌های «منطقی» CSS مثل margin-inline-start و padding-inline-end خودکار با جهت سازگار می‌شوند؛ ولی margin-left و margin-right فیزیکی هستند و تغییر نمی‌کنند.

به همین دلیل، هنگام استایل‌دهی تا حد امکان از ویژگی‌های منطقی استفاده می‌کنیم. در Tailwind هم معادل‌های منطقی مثل ms-* و me-* (فاصله‌ی ابتدا و انتها) وجود دارند. جزئیات را در درس «طراحی رابط کاربری فارسی و راست‌چین DevNotes» می‌بینیم.

نکته‌ی مهم برای DevNotes: کد باید چپ‌به‌راست بماند

وب‌سایت ما نوشته‌های فنی منتشر می‌کند و کد برنامه‌نویسی همیشه چپ‌به‌راست است. در یک صفحه‌ی راست‌چین، بلوک‌های کد ممکن است با جهت اشتباه نمایش داده شوند، مثلاً علامت‌گذاری و پرانتزها جابه‌جا دیده شوند. برای پیشگیری، می‌توان در app/globals.css برای کد جهت را صریحاً چپ‌به‌راست تعیین کرد:

css
/* app/globals.css */
pre,
code {
  direction: ltr;
  text-align: left;
}

این قاعده را الان به انتهای فایل اضافه کنید. در فصل‌های بعد وقتی نوشته‌ها را نمایش می‌دهیم از آن استفاده می‌کنیم. اگر فایل globals.css شما قواعد Tailwind یا متغیرهای رنگ دارد، آن‌ها را دست نزنید و این قاعده را فقط در انتها اضافه کنید.

نکته‌های مهم

  • فقط یک Root Layout: تگ‌های html و body فقط در Root Layout نوشته می‌شوند. اگر در Layout تودرتو یا صفحه آن‌ها را دوباره بنویسید، HTML نامعتبر می‌شود. اگر بعداً پروژه چند Root Layout داشته باشد (با Route Groups)، هرکدام این تگ‌ها را تعریف می‌کنند؛ این موضوع را در درس «Route Groups و سازمان‌دهی مسیرها» می‌بینیم.
  • جهت محلی: اگر بخش کوچکی از صفحه باید چپ‌به‌راست باشد (مثلاً یک متن انگلیسی طولانی)، می‌توانید روی همان عنصر dir="ltr" بگذارید، بدون اینکه جهت کل صفحه تغییر کند.
  • متن‌های ترکیبی: وقتی فارسی و انگلیسی یا عدد در یک جمله ترکیب می‌شوند، مرورگر الگوریتم خودکار جهت‌دهی دوطرفه (Bidi) را اجرا می‌کند. گاهی علائم نگارشی در انتهای جمله‌ی ترکیبی جای غیرمنتظره می‌گیرند. این رفتار خودِ مرورگر است، نه ایراد Next.js.
  • Layout و داده‌ی صفحه: Layout نمی‌تواند searchParams را به‌عنوان props دریافت کند؛ فقط صفحه می‌تواند. این نکته را در درس «searchParams و Query String» می‌بینیم.

جمع‌بندی

  • Root Layout فایل app/layout.tsx است؛ وجودش الزامی است، برای کل برنامه اعمال می‌شود و باید تگ‌های html و body را بسازد.
  • Layout صفحه‌ی درونی را از children می‌گیرد و هنگام جابه‌جایی بین صفحات مشترک باقی می‌ماند.
  • با lang="fa" زبان صفحه و با dir="rtl" جهت راست‌به‌چپ تعریف می‌شود؛ هر دو را روی html می‌گذاریم.
  • برای چیدمان راست‌چین از ویژگی‌های منطقی CSS استفاده می‌کنیم و بلوک‌های کد را با direction: ltr چپ‌به‌راست نگه می‌داریم.
  • فونت فارسی و Metadata کامل‌تر را در درس‌های بعدی تنظیم می‌کنیم.
  • در درس بعد، «Nested Layoutها»، Layoutهای مخصوص هر بخش از سایت را می‌سازیم.