Next.js چیست؟

در پاسخ به این سوال که Next.js چیست باید بگوییم که یک فریم‌ورک React برای توسعه قسمت فرانت‌اند وب است که عملکردهایی مانند رندر در سمت سرور و تولید سایت استاتیک را امکان‌پذیر می‌کند. در این مقاله قصد داریم تا مفاهیم مربوط به Next.js را باهم بررسی کنیم و بیشتر با این فریم‌ورک آشنا شویم.

همینطور در ویدیو Next.js چیست – بررسی امکانات و کاربردها کانال یوتیوب فرانت کست هم به این موضوع پرداخته‌ایم که مشاهده آن را پیشنهاد می‌کنیم.

منظور از رندر سمت سرور در Next.js چیست؟

در یک برنامه متداول React، کل برنامه لود شده و به کلاینت ارائه می‌شود. Next.js اجازه می‌دهد تا لود شدن صفحه اول توسط سرور انجام شود که این کار برای مبحث SEO و افزایش عملکرد بسیار موثر است.

مزایای دیگر Next.js

  • مسیریابی راحت‌تر بین صفحات
  • Api routeهای سمت سرور
  • تولید سایت به شکل استاتیک
  • دارای استقرار آسان

ساخت اولین پروژه Next.js

برای نصب و ساخت پروژه Next.js می‌توانیم از دستور npx به شکل زیر استفاده کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npx create-next-app my-app-name
npx create-next-app my-app-name
npx create-next-app my-app-name

و یا این که می‌توانیم پیش پیکربندی Tailwind CSS را به صورت زیر به‌کار بگیریم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npx create-next-app -e with-tailwindcss my-app-name
npx create-next-app -e with-tailwindcss my-app-name
npx create-next-app -e with-tailwindcss my-app-name

با انجام این کار یک پوشه می‌سازیم که همه فایل‌ها، تنظیمات و هر چیزی که برای راه‌اندازی برنامه Next.js نیاز داریم داخل آن ایجاد می‌شود.

پس از ساخت برنامه اکنون می‌توانیم آن را راه‌اندازی کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
cd your-app-name
npm run dev
cd your-app-name npm run dev
cd your-app-name
npm run dev

دستور بالا برنامه خالی Next.js ما را راه‌اندازی می‌کند که به‌طور پیش‌فرض یک صفحه خوش آمدگویی از قبل برای ما ایجاد شده است.

صفحات و مسیریابی بین آن‌ها

برای مدیریت مسیرها در Next.js مجبور نیستیم از کتابخانه مسیریابی استفاده کنیم. بلکه پیاده‌سازی مسیریابی Next.js بسیار آسان است.

هنگامی که یک برنامه Next.js جدید با دستور

create-next-app
create-next-appمی‌سازیم برنامه به‌طور پیش‌فرض یک پوشه به نام
pages
pagesایجاد می‌کند.

این پوشه

pages
pages مسئولیت مدیریت مسیرهای ما را برعهده دارد. بنابراین هر فایل کامپوننت React در پوشه به عنوان یک مسیر خاص در نظر گرفته می‌شود.

به عنوان مثال اگر پوشه حاوی فایل‌های زیر باشد:

  • index.js
    index.js
  • about.js
    about.js
  • blog.js
    blog.js

این فایل به طور خودکار به ۳ مسیر تبدیل می‌شود:

  • صفحه فهرست
    localhost/index
    localhost/index
  • صفحه درباره
    localhost/about
    localhost/about
  • صفحه وبلاگ
    localhost/blog
    localhost/blog

همانطور که می‌بینید قاعده کلی این مفهوم بسیار آسان است.

همچنین، اگر از مسیری بازدید کنیم که وجود نداشته باشد مانند

localhost/home
localhost/home، در این صورت Next.js به طور خودکار صفحه not found 404  را نشان خواهد داد.

در ادامه نمونه‌ای از صفحه

about.js
about.jsرا داریم. همانطور که مشاهده می‌کنید توضیح زیادی در مورد این صفحه مشخص نشده است، بلکه فقط یک کامپوننت functional معمولی React است.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function AboutPage() {
return (
<div>
<h1>About</h1>
</div>
)
}
export default AboutPage
function AboutPage() { return ( <div> <h1>About</h1> </div> ) } export default AboutPage
function AboutPage() {
    return (
        <div>
            <h1>About</h1>
        </div>
    )
}

export default AboutPage

مسیرهای تودرتو

برای ایجاد مسیرهای تودرتو همانند

localhost/blog/contact
localhost/blog/contact، باید یک پوشه فرعی بسازیم. به عنوان مثال:
pages/blog
pages/blog

در داخل آن پوشه می‌توانیم کامپوننت

contact.js
contact.jsرا ایجاد کرده و صفحه
localhost/blog/contact
localhost/blog/contactرا بسازیم.

اگر یک فایل

index.js
index.jsدر آن زیر پوشه ایجاد کنیم، Next.js از آن کامپوننت برای نشان دادن مسیر root ما استفاده می‌کند. مثلا:
localhost/blog
localhost/blogمسیر
pages/blog/index.js
pages/blog/index.jsرا رندر می‌کند.

اما اگر یک فایل در

pages/blog.js
pages/blog.jsو فایل دیگری در
pages/blog/index.js
pages/blog/index.jsایجاد کنیم، هر دو نشان دهنده یک مسیر یکسان
localhost/blog
localhost/blogهستند. در این صورت Next.js فقط فایل
blog.js
blog.jsرا رندر خواهد کرد.

اما درمورد مسیرهای پویا راه‌حل دیگری وجود دارد. به عنوان مثال مسیر هر پست وبلاگ که یک مسیر منحصربفرد است می‌تواند شبیه مثال زیر باشد:

  • localhost/blog/my-first-blog
    localhost/blog/my-first-blog
  • localhost/blog/my-second-blog-post
    localhost/blog/my-second-blog-post

در Next.js می‌توانیم با استفاده از علامت

[]
[]یک مسیر پویا ایجاد کنیم. مثلا:
pages/blog/[slug].js
pages/blog/[slug].js

متغیر Slug را می‌توانیم با استفاده از هوک useRoute از route استخراج کرده و در مسیر پویایی که داریم مورد استفاده قرار می‌دهیم.

در ادامه نمونه‌ای از صفحه

[slug].js
[slug].jsرا بررسی می‌کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { useRouter } from 'next/router'
function PostPage() {
const router = useRouter()
return (
<div>
<h1>My post: {router.query.slug}</h1>
</div>
)
}
export default PostPage
import { useRouter } from 'next/router' function PostPage() { const router = useRouter() return ( <div> <h1>My post: {router.query.slug}</h1> </div> ) } export default PostPage
import { useRouter } from 'next/router'

function PostPage() {
    const router = useRouter()
    return (
        <div>
            <h1>My post: {router.query.slug}</h1>
        </div>
    )
}

export default PostPage

این یک مثال بیسیک است. در یک برنامه واقعی، از متغیر Slug برای لود کردن فایل پست یا جستجو در پایگاه داده برای پست مربوط به آن استفاده می‌شود.

لینک‌های مسیرها

اکنون اولین مسیر خود را ایجاد کردیم، سوالی که ممکن است پیش بیاید این است که چگونه می‌توانیم صفحات را از طریق لینک‌ به مسیرها ارتباط دهیم؟ برای انجام این کار به

next/link
next/linkنیاز داریم. در مثال پایین می‌خواهیم از صفحه اصلی به صفحه about لینک ایجاد کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import Link from 'next/link'
export default function Home() {
return (
<div>
<h1>Home</h1>
<Link href="about">About</Link>
</div>
)
}
import Link from 'next/link' export default function Home() { return ( <div> <h1>Home</h1> <Link href="about">About</Link> </div> ) }
import Link from 'next/link'

export default function Home() {
  return (
    <div>
      <h1>Home</h1>
      <Link href="about">About</Link>
    </div>
  )
}

اگر می‌خواهیم در صفحه about لینکی ایجاد کنیم تا به صفحه اصلی بازگردیم می‌توانیم به شکل زیر عمل کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<Link href="/">Home</Link>
<Link href="/">Home</Link>
<Link href="/">Home</Link>

همینطور اگر بخواهیم لینکی که داریم استایل مخصوص خود را داشته باشد باید از سینتکس زیر استفاده کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<Link href='/about'>
<a className="text-blue-500">About this project</a>
</Link>
<Link href='/about'> <a className="text-blue-500">About this project</a> </Link>
<Link href='/about'>
    <a className="text-blue-500">About this project</a>
</Link>

تغییر مسیر

اگر بخواهیم به شکل اجباری به یک صفحه خاص تغییر مسیر دهیم، به عنوان مثال هنگام کلیک روی یک دکمه به یک صفحه خاص هدایت شویم برای این کار می‌توانیم از

router.push
router.pushاستفاده کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import Link from 'next/link'
import { useRouter } from 'next/router'
function About() {
const router = useRouter()
return (
<div>
<h1>About Page</h1>
<p>This is the about page</p>
<button onClick={() => router.push('/')}>Return to home</button>
</div>
)
}
import Link from 'next/link' import { useRouter } from 'next/router' function About() { const router = useRouter() return ( <div> <h1>About Page</h1> <p>This is the about page</p> <button onClick={() => router.push('/')}>Return to home</button> </div> ) }
import Link from 'next/link'
import { useRouter } from 'next/router'

function About() {
  const router = useRouter()

  return (
    <div>
      <h1>About Page</h1>
      <p>This is the about page</p>
      <button onClick={() => router.push('/')}>Return to home</button>
    </div>
  )
}

محل قرارگیری کامپوننت‌ها

اغلب همه ما می‌خواهیم تا یک کامپوننت و یا یک فایل layout ایجاد کنیم. به عنوان مثال یک کامپوننت برای رندر کردن navbar.

تا به حال فقط از پوشه

pages
pagesاستفاده کرده‌ایم اما اگر نخواهیم کامپوننتی که داریم یک صفحه با مسیر مجزا باشد، مثلا نمی‌خواهیم کاربر صفحه‌ای مانند
localhost/navbar
localhost/navbarرا باز کند در این صورت باید تمام کامپوننت‌هایی که می‌خواهیم صفحه مستقلی برای خود نداشته باشند را در یک پوشه دیگر قرار دهیم.

طبق قرارداد موجود، Next.js اکثرا از پوشه‌ای با نام

components
componentsاستفاده می‌کند و این پوشه در پوشه اصلی برنامه‌های ما ایجاد می‌شود. بنابراین برای مثال اگر می‌خواهیم یک کامپوننت layout بسازیم، می‌توانیم آن را در یک پوشه کامپوننت جدید ایجاد کنیم:
/components/Layout.js
/components/Layout.js

از آن کامپوننت React می‌توانیم در هر قسمت از برنامه خود استفاده کنیم، اما به عنوان یک صفحه دارای مسیر مرجع نخواهد بود.

منظور از کامپوننت Head در Next.js چیست؟

Next.js سمت سرور برای لود شدن صفحه اول را رندر می‌کند. برای انجام این کار، باید تغییراتی را html صفحه خود را اعمال کنیم از جمله قسمت هدر.

برای اینکه تگ‌های بخش هدر مانند title یا meta را تکمیل کنیم باید از کامپوننت هد Next.js استفاده کنیم. در مثال زیر با استفاده از کامپوننت Head یک کامپوننت Layout ایجاد کرده‌ایم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// components/Layout.js
import Head from 'next/head'
function Layout({title, keywords, description, children}) {
return (
<div>
<Head>
<title>{title}</title>
<meta name='description' content={description}/>
<meta name='keywords' content={keywords}/>
</Head>
{children}
</div>
)
}
export default Layout
Layout.defaultProps = {
title: 'This is my app title',
description: 'This is my app description',
keywords: 'web, javascript, react, next'
}
// components/Layout.js import Head from 'next/head' function Layout({title, keywords, description, children}) { return ( <div> <Head> <title>{title}</title> <meta name='description' content={description}/> <meta name='keywords' content={keywords}/> </Head> {children} </div> ) } export default Layout Layout.defaultProps = { title: 'This is my app title', description: 'This is my app description', keywords: 'web, javascript, react, next' }
// components/Layout.js
import Head from 'next/head'
function Layout({title, keywords, description, children}) {
    return (
        <div>
            <Head>
                <title>{title}</title>
                <meta name='description' content={description}/>
                <meta name='keywords' content={keywords}/>
            </Head>
            {children}
        </div>
    )
}

export default Layout

Layout.defaultProps = {
    title: 'This is my app title',
    description: 'This is my app description',
    keywords: 'web, javascript, react, next'
}

ساخت صفحه سفارشی ۴۰۴ not found

در Next.js امکان ایجاد یک صفحه سفارشی ۴۰۴ not found وجود دارد. زیرا ممکن است بخواهیم پیامی که می‌خواهیم به کاربر نمایش دهیم را شخصی‌سازی کنیم و یا layout صفحه خود را اضافه کنیم.

برای این کار باید فایل

۴۰۴٫js
۴۰۴٫jsرا در پوشه
pages
pagesایجاد کنیم. در این صورت هنگام مواجهه با خطای ۴۰۴، Next.js به طور خودکار به این صفحه هدایت می‌شود.

در ادامه نمونه‌ای از توسعه صفحه ۴۰۴ را مطرح کرده‌ایم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// pages/404.js
import Layout from '../components/Layout'
function NotFoundPage() {
return (
<Layout>
Sorry the page you are looking is no where to be found.
</Layout>
)
}
export default NotFoundPage
// pages/404.js import Layout from '../components/Layout' function NotFoundPage() { return ( <Layout> Sorry the page you are looking is no where to be found. </Layout> ) } export default NotFoundPage
// pages/404.js
import Layout from '../components/Layout'

function NotFoundPage() {
    return (
        <Layout>
            Sorry the page you are looking is no where to be found.        
        </Layout>
    )
}

export default NotFoundPage

وارد کردن shortcut alias

همانطور که برنامه ما در حال رشد است می‌توانیم برخی از کامپوننت‌ها را به شکل تودرتو در ساختار پوشه برنامه قرار دهیم.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import Layout from '../../components/Layout'
import Layout from '../../components/Layout'
import Layout from '../../components/Layout'

بنابراین می‌توانیم shortcutای ایجاد کنیم تا آدرس‌دهی را کوتاه‌تر کند و همان نتیجه‌ی قبلی را به ما بدهد:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import Layout from '@/components/Layout'
import Layout from '@/components/Layout'
import Layout from '@/components/Layout'

کاراکتر @ یک دستور shortcut است.

برای ایجاد این shortcut و موارد دیگر، باید یک فایل با نام

jsconfig.json
jsconfig.jsonدر root برنامه خود ایجاد کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// jsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/components/*": ["components/*"],
}
}
}
// jsconfig.json { "compilerOptions": { "baseUrl": ".", "paths": { "@/components/*": ["components/*"], } } }
// jsconfig.json
{
    "compilerOptions": {
        "baseUrl": ".",
        "paths": {
            "@/components/*": ["components/*"],
        }
    }
}

Fetch کردن داده‌های سمت سرور

به جای fetch کردن داده‌ها در سمت کلاینت، Next.js می‌تواند رندر سمت سرور را در یک صفحه فعال کند و امکان جمع‌بندی داده‌های اولیه را به ما بدهد، این به معنای فرستادن صفحه با داده‌هایی است که قبلاً از طریق سرور ارسال شده است.

برای پیاده‌سازی fetch کردن داده‌های سمت سرور، ۲ راه داریم:

  1. Fetch کردن داده‌ها در هر درخواست
  2. Fetch کردن داده‌ها فقط یک بار در زمان ساخت (سایت استاتیک)

Fetch کردن داده‌ها در هر درخواست

برای رندر شدن سمت سرور در هر درخواست، باید از تابع

getServerSideProps
getServerSidePropsاستفاده کنیم. می‌توانیم این تابع را در انتهای فایل کامپوننت خود اضافه کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
export async function getServerSideProps() {
const res = await fetch(`http://server-name/api/items`)
const items = await res.json()
return {
props: {items},
}
}
export async function getServerSideProps() { const res = await fetch(`http://server-name/api/items`) const items = await res.json() return { props: {items}, } }
export async function getServerSideProps() {
  const res = await fetch(`http://server-name/api/items`)
  const items = await res.json()
  return {
    props: {items}, 
  }
}

اگر آن تابع در فایل کامپوننت ما وجود داشته باشد، Next.js به طور خودکار props کامپوننت را با آبجکت

getServerSideProps
getServerSidePropsپر می‌کند.

Fetch کردن داده‌ها فقط یک بار در زمان ساخت

برای رندر شدن سمت سرور در زمان ساخت باید از تابع

getStaticProps
getStaticPropsاستفاده کنیم. می‌توانیم این تابع را در انتهای فایل کامپوننت خود اضافه کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
export async function getStaticProps() {
const res = await fetch('http://server-name/api/items')
const items = await res.json()
return {
props: {items},
}
}
export async function getStaticProps() { const res = await fetch('http://server-name/api/items') const items = await res.json() return { props: {items}, } }
export async function getStaticProps() {
  const res = await fetch('http://server-name/api/items')
  const items = await res.json()
  return {
    props: {items}, 
  }
}

همچنین ممکن است بخواهیم داده‌ها را در زمان ساخت اما برای یک مسیر پویا fetch کنیم، مثلا

/posts/my-first-post
/posts/my-first-post.

فرض کنید یک صفحه با نام

posts/[slug].js
posts/[slug].jsداریم که به ما مسیرهای
posts/my-first-post
posts/my-first-post،
posts/my-second-blog
posts/my-second-blogو غیره را می‌دهد. در این شرایط می‌توانیم از
getStaticPaths
getStaticPathsبرای ایجاد تمام آن مسیرهای فرعی در زمان ساخت استفاده کنیم.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
export async function getStaticPaths() {
const res = await fetch(`${API_URL}/posts`)
const posts = await res.json()
const paths = posts.map(post => ({params: {slug: post.slug}}))
return {
paths,
fallback: true,
}
}
export async function getStaticProps({params: {slug}}) {
const res = await fetch(`${API_URL}/posts?slug=${slug}`)
const posts = await res.json()
return {
props: {
post: posts[0]
}
}
}
export async function getStaticPaths() { const res = await fetch(`${API_URL}/posts`) const posts = await res.json() const paths = posts.map(post => ({params: {slug: post.slug}})) return { paths, fallback: true, } } export async function getStaticProps({params: {slug}}) { const res = await fetch(`${API_URL}/posts?slug=${slug}`) const posts = await res.json() return { props: { post: posts[0] } } }
export async function getStaticPaths() {
    const res = await fetch(`${API_URL}/posts`)
    const posts = await res.json()
    const paths = posts.map(post => ({params: {slug: post.slug}}))
    return {
        paths,
        fallback: true,
    }
}
export async function getStaticProps({params: {slug}}) {
    const res = await fetch(`${API_URL}/posts?slug=${slug}`)
    const posts = await res.json()
    return {
        props: {
            post: posts[0]
        }
    }
}

منظور از بهینه‌سازی تصویر در Next.js چیست؟

Next.js دارای یک کامپوننت داخلی و بهینه‌سازی خودکار تصویر است اما منظور از این کامپوننت چیست؟ کامپوننت تصویر Next.js که

next/image
next/imageاست، یک فرمت از المنت HTML می‌باشد و برای وب مدرن طراحی شده است.

تصاویر به‌طور پیش‌فرض دارای Lazy loading هستند، یعنی این که آن‌ها هنگام نیاز و با اسکرول شدن در صفحه نمایش لود می‌شوند.

برای این کار ابتدا کامپوننت

Image
Imageرا import می‌کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import Image from 'next/image'
import Image from 'next/image'
import Image from 'next/image'

سپس به صورت زیر از آن استفاده می‌کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<Image
src="/image.png"
alt="Picture of the author"
width={500}
height={500}
/>
<Image src="/image.png" alt="Picture of the author" width={500} height={500} />
<Image
  src="/image.png"
  alt="Picture of the author"
  width={500}
  height={500}
/>

برای اینکه اطلاعات بیشتری درمورد کامپوننت Image در Next.js بدست بیاورید مطالعه این لینک می‌تواند مفید باشد.

 

دیدگاه‌ها:

افزودن دیدگاه جدید