آموزش کامل کار با Next.js در یک مقاله

!Next.js in One Article

22 اردیبهشت 1401
Next.js در یک مقاله

قبلا در مقاله ای با نام «Next.js چیست و چه کاربردی دارد؟» درباره ماهیت فریم ورک Next.js و ویژگی های آن صحبت کردیم. در این مقاله می خواهیم به صورت کلی و عملی به آموزش next js بپردازیم.

مزیت اصلی next.js ارائه قابلیت Server-Side Rendering است که یعنی صفحات شما در سمت سرور ساخته شده و به صورت کامل شده به سمت کاربر ارسال می شوند در حالی که در برنامه های عادی و تک صفحه ای React داده ها از API دریافت شده و روی مرورگر کاربر توسط جاوا اسکریپت از صفر ساخته می شود. طبیعتا چنین موضوعی SEO شما را از دو جهت خراب می کند:

  • در برخی از موارد crawler ها (ربات های موتورهای جست و جو) نمی توانند محتویات صفحه را بخوانند.
  • در اکثر موارد زمان اولیه بارگذاری صفحه برای برنامه های SPA بسیار زیاد است اما این زمان برای موتورهای جست و جو اهمیت زیادی دارد.

پیش نیاز: برای مطالعه این مقاله باید حتما با مفاهیم ساده جاوا اسکریپت آشنا باشید. همچنین باید با React در حد متوسط آشنا باشید چرا که Next.js از React استفاده می کند. در نهایت درک مباحثی مانند API و نحوه ساخت و تعامل با آن نیز ضروری است.

نصب اولیه Next.js

بهترین راه برای شروع یک پروژه Next.js این است که از پروژه create-next-app استفاده کنیم. برای این کار ترمینال یا CMD خود را در پوشه مورد نظرتان باز کرده و سپس دستور زیر را اجرا کنید:

npx create-next-app --typescript

اضافه کردن فلگ typescript-- در انتهای آن باعث می شود که نسخه تایپ اسکریپت آن (به جای جاوا اسکریپت خالی) به ما داده شود. با اجرای دستور بالا از شما پرسیده می شود که نام پروژه شما چیست؟

What is your project named? › next-demo

همانطور که می بینید من نام next-demo را انتخاب کرده ام اما طبیعتا شما می توانید هر نامی را انتخاب کنید. با فشردن کلید enter فرآیند نصب پکیج های react و react-dom و next شروع می شود. برای شروع هر پروژه next ای به این سه پکیج نیاز داریم. پس از اتمام فرآیند نصب چنین نتیجه ای را مشاهده می کنید:

Success! Created next-demo at /media/amir/Development/Roxo Academy/next-demo

Inside that directory, you can run several commands:




  npm run dev

    Starts the development server.




  npm run build

    Builds the app for production.




  npm start

    Runs the built app in production mode.




We suggest that you begin by typing:




  cd next-demo

  npm run dev

با این حساب وارد پوشه جدید next-demo شده و دستور npm run dev را اجرا می کنیم. با اجرای این دستور نتیجه زیر را در ترمینال می گیرید:

ready - started server on 0.0.0.0:3000, url: http://localhost:3000

info  - Using webpack 5. Reason: Enabled by default https://nextjs.org/docs/messages/webpack5

event - compiled successfully

یعنی از این بخش به بعد برنامه شما در آدرس http://localhost:3000 در مرورگر قابل مشاهده است. اگر در حال حاضر به این آدرس بروید صفحه ای را می بینید که next به عنوان نمونه در پکیج create-next-app قرار داده است.

ساختار پوشه next-demo

بیایید به ساختار پوشه ایجاد شده (next-demo) توجه کنیم. اولین پوشه ای که در next-demo وجود دارد پوشه public است که محتویات استاتیک شما را در خود دارد، یعنی محتوایی که به صورت عمومی در دسترس تمام افراد است. در حال حاضر در این پوشه دو فایل وجود دارد که نام یکی از آن ها favicon.ico است. از آنجایی که این فایل در پوشه public قرار گرفته است ما می توانیم مستقیما به آدرس http://localhost:3000/favicon.ico در مرورگر رفته و آن را مشاهده کنیم. با این حساب باید به شدت نسبت به این موضوع حساس باشید چرا که هر فایلی در پوشه public در مرورگر نمایش داده خواهد شد.

پوشه بعدی پوشه styles است که حاوی استایل های ما است. در ابتدا یک فایل به نام globals.css را داریم که حاوی استایل های سراسری برنامه ما است (روی تمام صفحات پیاده می شود) و سپس فایل دیگری به نام Home.module.css را داریم که فقط روی صفحه home اعمال می شود. چطور؟ next می تواند فایل های CSS را به یک کامپوننت خاص محدود کند. برای انجام این کار باید نام فایل های CSS خود را بر اساس الگوی زیر انتخاب کنید:

componentName.module.css

به طوری که به جای componentName نام کامپوننت react خودتان را بنویسید. به طور مثال اگر به فایل index.tsx از پوشه pages بروید، می بینید که نام کامپوننت درون آن Home است:

import Head from 'next/head'

import Image from 'next/image'

import styles from '../styles/Home.module.css'

export default function Home() {

// بقیه کد ها

شما اجازه ندارید فایل های سراسری CSS را در چنین کامپوننت هایی وارد کنید، بلکه فقط فایل هایی که آخر نامشان module.css دارند قابل وارد شدن در کامپوننت ها هستند. در کد بالا می بینید که Home.module.css درون این کامپوننت import شده است.

پوشه بعدی pages است که سیستم routing در فریم ورک next را دارد! یعنی چه؟ یعنی هر کامپوننتی که درون آن ایجاد کنید، یک صفحه از برنامه شما خواهد بود. برای اثبات این موضوع من یک فایل به نام about.tsx را در پوشه pages می سازم. درون این فایل یک کامپوننت بسیار ساده react را ایجاد می کنیم:

const about = () => {

  return (

    <div>

      <h1>About Us</h1>

    </div>

  );

};




export default about;

همانطور که می بینید این فایل فقط یک کامپوننت ساده با یک تگ <h1> را دارد. حالا اگر در مرورگر به آدرس http://localhost:3000/about برویم همین کامپوننت را مشاهده می کنیم! در طول این مقاله توضیحات بیشتری در رابطه با نحوه ایجاد routing خواهیم داد.

در نهایت یک پوشه مخفی به نام next. دارید که مربوط به کش next بوده و معمولا هیچ وقت با آن کاری نداریم چرا که به طور خودکار توسط next کنترل می شود. درباره پوشه های api و فایل app.tsx_ نیز بعدا توضیح خواهم داد.

<Head> در next

ابتدا به فایل index.tsx در پوشه pages بروید و تمام محتوای آن به غیر از div اصلی را پاک کنید. در این فایل عنصری به نام next/head وارد شده است:

import Head from "next/head";

این عنصر خاص باعث می شود بخش <head> از صفحه HTML خود را کنترل کنید. با این حساب من محتویات این کامپوننت را بدین شکل تغییر می دهم:

import Head from "next/head";




export default function Home() {

  return (

    <div>

      <Head>

        <title>My Next.js website</title>

        <meta name="description" content="a demo for roxo.ir/blog" />

        <meta name="keywords" content="web development, programming" />

        <link rel="icon" href="/favicon.ico" />

      </Head>




      <h1>Home Page</h1>

    </div>

  );

}

همانطور که می بینید من در این تگ <head> که در اصل یک کامپوننت است مقادیری مانند title و تگ های meta را مشخص کرده ام. شما می توانید این کار را برای هر صفحه ای که می خواهید انجام بدهید.

تعریف ساختار صفحات با app.tsx_

اگر به محتویات فایل app.tsx_ در پوشه pages نگاهی بیندازید متوجه خواهید شد که این فایل یک کامپوننت پدر به نام MyApp است که تمام پروژه شما درون آن نمایش داده می شود. یعنی تمام کامپوننت های دیگر فرزند این کامپوننت اصلی هستند. با این حساب اگر می خواهید عنصر خاصی در تمام صفحات نمایش داده شود باید آن را در این فایل اضافه کنید. مثلا اگر می خواهید تمام صفحات شما header و footer داشته باشند، این فایل بهترین راه انجام آن است. همچنین در این فایل است که استایل های سراسری وارد می شوند. هر فایل CSS ای که با نام module.css به پایان نرسد یک فایل سراسری محسوب می شود.

من در پوشه اصلی برنامه (next-demo) یک پوشه به نام components ایجاد می کنم. این پوشه مخصوص کامپوننت هایی است که یک صفحه خاص نیستند. مثلا اگر بخواهیم یک کامپوننت برای لینک هایمان ایجاد کنیم باید آن را در این پوشه تعریف کنیم چرا که یک دکمه خالی یک صفحه جداگانه نخواهد داشت. حالا درون این پوشه یک فایل به نام Layout.tsx را ایجاد کنید.

در مرحله بعدی به فایل Home.module.css بروید و نام آن را به Layout.module.css تغییر بدهید. چرا؟ به دلیل اینکه می خواهم از این استایل ها در کامپوننت Layout استفاده کنم. من می خواهم تمام محتوای ما در تمام صفحات درون تگ های main قرار بگیرد بنابراین در فایل Layout.tsx چنین کاری را انجام می دهم:

import { ReactNode } from "react";

import styles from "../styles/Layout.module.css";




interface PropType {

  children: ReactNode;

}




const Layout = ({ children }: PropType) => {

  return (

    <div className={styles.container}>

      <main className={styles.main}>{children}</main>

    </div>

  );

};




export default Layout;

همانطور که می بینید در اینجا یک کامپوننت به نام Layout را داریم که یک div اصلی داشته و درون خود تگ های main را دارد. تگ های div و main با کلاس های موجود در فایل Layout.module.css استایل دهی شده اند. برای انجام چنین کاری باید نام کلاس خود را به صورت یک خصوصیت از شیء styles دریافت کنید. البته من نام آن را styles گذاشته ام اما شما می توانید هر نام دیگری را برایش انتخاب کنید. این کامپوننت یک prop به نام children می گیرد که می تواند هر کامپوننت دیگری باشد و سپس آن را درون main نمایش می دهد.

در مرحله بعدی به فایل app.tsx_ برمی گردیم و آن را بدین شکل ویرایش می کنیم:

import "../styles/globals.css";

import type { AppProps } from "next/app";

import Layout from "../components/Layout";




function MyApp({ Component, pageProps }: AppProps) {

  return (

    <Layout>

      <Component {...pageProps} />

    </Layout>

  );

}

export default MyApp;

من در اینجا کامپوننت Layout خودم را وارد کرده ام و سپس کل برنامه را (کامپوننت Component) درون همین Layout قرار داده ام. با این کار تمام محتوای برنامه ما درون تگ های main قرار می گیرند. در حال حاضر اگر به مرورگر توجه کنید متوجه خواهید شد که محتوای صفحه دقیقا در وسط صفحه قرار گرفته است که مورد پسند من نیست بنابراین به Layout.module.css رفته و استایل های کلاس های container و main را بدین شکل ویرایش می کنم:

.container {

  min-height: 100vh;

  padding: 0 0.5rem;

  display: flex;

  flex-direction: column;

  justify-content: flex-start;

  align-items: center;

  height: 100vh;

}




.main {

  padding: 5rem 0;

  flex: 1;

  display: flex;

  flex-direction: column;

  justify-content: flex-start;

  align-items: center;

  font-size: 1.25rem;

}

با تغییر دادن justify-content به مقدار flex-start محتوا را به صورت طبیعی نمایش می دهیم تا در وسط صفحه نباشند.

ساخت نوار navigation

سوال بعدی در پروژه ما این است که چطور می توانیم به صفحات مختلف لینک بدهیم؟ برای نمایش دادن لینک ها بهترین کار ساختن یک نوار navigation است. برای انجام این کار ابتدا به پوشه styles رفته و فایلی جدید به نام Nav.module.css را ایجاد می کنیم. من محتویات این فایل را به صورت آماده برایتان قرار می دهم:

.nav {

  height: 50px;

  padding: 10px;

  background: #000;

  color: #fff;

  display: flex;

  align-items: center;

  justify-content: flex-start;

}




.nav ul {

  display: flex;

  justify-content: center;

  align-items: center;

  list-style: none;

}




.nav ul li a {

  margin: 5px 15px;

}

حالا در پوشه components یک فایل به نام Navbar.tsx ایجاد می کنیم و کامپوننت navigation خود را می نویسیم:

import Link from "next/link";

import navStyles from "../styles/Nav.module.css";




const Navbar = () => {

  return (

    <nav className={navStyles.nav}>

      <ul>

        <li>

          <Link href="/">Home</Link>

        </li>

        <li>

          <Link href="/about">About Us</Link>

        </li>

      </ul>

    </nav>

  );

};




export default Navbar;

دقیقا مانند react router زمانی که از next استفاده می کنید باید از عنصری ویژه به نام Link استفاده نمایید. من در این کامپوننت عنصر Nav با کلاس های مناسبش را قرار داده ام و دو لینک به صفحات اصلی (Home) و درباره ما (about us) دارم. از آنجایی که می خواهیم این نوار در تمام صفحات باید باید به فایل Layout.tsx رفته و این کامپوننت را در آنجا استفاده کنیم:

import { ReactNode } from "react";

import styles from "../styles/Layout.module.css";

import Navbar from "./Navbar";




interface PropType {

  children: ReactNode;

}




const Layout = ({ children }: PropType) => {

  return (

    <>

      <Navbar />




      <div className={styles.container}>

        <main className={styles.main}>{children}</main>

      </div>

    </>

  );

};




export default Layout;

از آنجایی که هر کامپوننت react باید یک عنصر ریشه ای داشته باشد بنابراین من از یک fragment استفاده کرده ام (عناصر خالی <>). چرا؟ به دلیل اینکه نمی خواهیم نوار navigation درون تگ های main قرار بگیرد. حالا اگر به مرورگر خود نگاه کنید باید نوار navigation را داشته باشید.

ساخت یک Header ساده

در مرحله بعدی می خواهیم یک Header را برای سایت خود ایجاد کنیم. برای این کار ابتدا فایل Header.module.css را تعریف می کنم و استایل های زیر را در آن قرار می دهم:

.title a,

.title span {

  color: #0070f3;

  text-decoration: none;

}




.title a:hover,

.title a:focus,

.title a:active {

  text-decoration: underline;

}




.title {

  margin: 0;

  line-height: 1.15;

  font-size: 4rem;

}




.title,

.description {

  text-align: center;

}




.description {

  line-height: 1.5;

  font-size: 1.5rem;

}

این استایل ها را از فایل Layout.module.css کات کرده ام چرا که نیازی به آن ها در آن فایل نداریم. همچنین سایت ما footer نخواهد داشت بنابراین تمام استایل های footer. را نیز از Layout.module.css حذف کرده ام.

پس از انجام این کار، دوباره به پوشه components رفته و یک فایل جدید به نام Header.tsx را می سازیم:

import headerStyles from "../styles/Header.module.css";




const Header = () => {

  return (

    <div>

      <h1 className={headerStyles.title}>

        <span>Roxo </span>

        Next.js Demo

      </h1>

      <p className={headerStyles.description}>

        Keep in touch with the programming world

      </p>

    </div>

  );

};




export default Header;

Header سایت ما به همین سادگی تعریف شده است و فقط یک عنوان به همراه توضیحات آن می باشد. من شخصا می خواهم Header درون تگ های main باشد بنابراین به Layout.tsx رفته و این کامپوننت را وارد آن بخش می کنیم:

import { ReactNode } from "react";

import styles from "../styles/Layout.module.css";

import Navbar from "./Navbar";

import Header from "./Header";




interface PropType {

  children: ReactNode;

}




const Layout = ({ children }: PropType) => {

  return (

    <>

      <Navbar />




      <div className={styles.container}>

        <main className={styles.main}>

          <Header />

          {children}

        </main>

      </div>

    </>

  );

};




export default Layout;

در حال حاضر اگر به مرورگر بروید header سایت خود را مشاهده می کنید:

header سایت تکمیل شده است
header سایت تکمیل شده است

Document در Next.js چیست؟

Next.js قابلیت خاصی به نام document دارد که به شما اجازه می دهد تگ های اصلی یک صفحه HTML (یعنی تگ های <html> و <body>) را شخصی سازی کنید. به طور مثال بسیار پیش می آید که در زبان فارسی یک خصوصیت به نام lang را روی تگ <html> ویرایش کرده و آن را روی fa بگذاریم.

هشدار: این فایل فقط در سمت سرور پردازش می شود و روی تمام سایت شما تاثیر خواهد گذاشت. در صورتی که نیازی به ویرایش آن ندارید به هیچ وجه آن را ایجاد نکنید چرا که باعث خراب شدن سایت خود می شوید.

در صورتی که بخواهیم چنین کاری را انجام بدهیم باید فایلی به نام document.js_ را در مسیر pages/_document.js ایجاد کنیم. در حال حاضر ابتدا این فایل را در پوشه pages ایجاد کنید و سپس سرور خود را متوقف کرده و دوباره اجرا کنید (npm run dev). پس از انجام این کار دوباره به آدرس اصلی سایت خود در مرورگر (http://localhost:3000) بروید. با انجام این کار می بینید که یک Server Error دریافت کرده اید. چرا؟ به دلیل اینکه فایل document.js_ خالی است.

شما باید در این فایل ساختار کلی صفحات وب خود (تگ های اصلی <html> و امثال آن) را مشخص کنید. من محتوای این فایل را به شکل زیر می نویسم:

import Document, { Html, Head, Main, NextScript } from "next/document";




class MyDocument extends Document {

  render() {

    return (

      <Html lang="fa">

        <Head />

        <body>

          <Main />

          <NextScript />

        </body>

      </Html>

    );

  }

}




export default MyDocument;

ساختار فایل شما نیز باید مثل فایل بالا باشد، یعنی یک کلاس به نام دلخواه تعریف کنید که کلاس اصلی Document را extend کند. در مرحله بعدی ساختار کلی صفحات HTML خود را تعریف می کنید که شامل تگ های html و head و body و غیره است. این بخش به سلیقه شما بستگی دارد به شرطی که تگ های اصلی مانند html و head و body تعریف شده باشند. تنها چیزی که من به این فایل اضافه کرده ام، خصوصیت lang به تگ html است که فقط به عنوان یک مثال برای شما است.

یک بار سرور را ریستارت کرده و دوباره به صفحه http://localhost:3000 بروید. این بار باید خصوصیت lang را روی html ببینید.

Pre-rendering چیست؟

قبل از اینکه وارد مباحث بعدی شویم باید با مبحثی به نام pre-rendering در next.js آشنا شوید چرا که بخش بسیار مهمی از کل فرآیند توسعه را تشکیل می دهد. در حالت عادی و در برنامه های SPA داده ها از سمت سرور دریافت شده و سپس جاوا اسکریپت، کد های HTML مورد نظر را از صفر در مرورگر کاربر می سازد.

در Next، کد های HTML تمام صفحات وب از قبل ساخته و آماده می شوند. ما به این پروسه pre-rendering می گوییم. مزیت اصلی pre-rendering امتیاز بهتر سئو و همچنین سرعت بالاتر سایت است چرا که بخش اعظم کد ها از قبل ساخته شده اند. در مرحله بعدی هر صفحه HTML با کمترین میزان کد های جاوا اسکریپتی لازم جفت می شود. حالا زمانی که مرورگر صفحه HTML را دریافت می کند، کد های کوچک جاوا اسکریپتی همراه آن صفحه در آن اجرا شده و صفحه را زنده و تعامل پذیر می کنند. به این فرآیند hydration می گوییم.

در next دو نوع pre-rendering وجود دارد:

  • Static Generation: این روش توسط تیم next توصیه می شود. در این حالت صفحات HTML در build time (زمانی که دستور npm run build را اجرا کرده و سایت در حال ساخته شدن توسط next است) ساخته می شوند و از آن به بعد برای هر درخواست مورد استفاده قرار می گیرند.
  • Server-side Rendering: در این روش، صفحات HTML برای هر درخواست از صفر ساخته می شوند.

نکته جالب اینجاست که Next.js به شما اجازه می دهد هر کدام از این دو روش را برای صفحات مختلف خود انتخاب کنید. مثلا می توانید از Static Generation برای اکثر صفحات خود استفاده کرده اما بعضی از صفحات را از نوع Server-side Rendering ایجاد کنید چرا که در بعضی صفحات هیچ راهی به جز بازسازی صفحه از صفر نیست. در نهایت باید بگویم که روش سومی از نمایش داده ها به نام Client-side Rendering است که دقیقا مانند برنامه های SPA عادی است اما من آن را در لیست بالا نیاورده ام چرا که دیگر  pre-rendering محسوب نمی شود.

تمام کار هایی که ما تا این لحظه انجام داده ایم از نوع Static Generation بوده اند که به داده ای خارجی نیاز نداشته اند اما حتما می دانید که بسیاری از صفحات وب نیاز به داده های خارجی دارند (مثلا باید با یک API دیگر صحبت کنند). اگر وب سایت شما یک وبلاگ یا یک فروشگاه اینترنتی باشد استفاده از Static Generation ایده آل خواهد بود چرا که اکثر مطالب سایت ثابت هستند اما اگر وب سایت شما صفحاتی دارد که داده ها دائما در آن در حال تغییر هستند چطور؟ در این حالت باید از یکی از دو روش زیر استفاده کنید:

  • Client-side Rendering: در این روش بخش های متغیر صفحه را pre-rendering نمی کنیم بلکه آن ها را با جاوا اسکریپت و در مرورگر کاربر می سازیم. این روش تقریبا معادل روش کار صفحات SPA عادی است.
  • Server-Side Rendering: هر صفحه برای هر درخواست از صفر باز سازی می شود. طبیعتا این روش سرعت کمتری نسبت به روش های دیگر دارد.

حالا که با این مفاهیم آشنا شده ایم باید به دنبال راه حل باشیم. اگر صفحه ما به داده های خارجی وابسته است چطور می توانیم این مسئله را حل کنیم؟

مبحث data fetching در next

در Next.js توابع خاصی وجود دارند که به ما اجازه می دهند داده های خارجی را دریافت کرده و در صفحات خود نمایش بدهیم. به این مبحث data fetching گفته می شود که سه متد اصلی دارد:

  • متد getStaticProps: این متد که در دسته Static Generation قرار دارد به شما اجازه می دهد داده ها را در هنگام build time دریافت کنید.
  • متد getStaticPaths: این متد که در دسته Static Generation قرار دارد به شما اجازه می دهد در هنگام استفاده از مسیر های پویا (dynamic routes) داده ها را دریافت و بر اساس آن صفحات خود را بسازید.
  • متد getServerSideProps: این متد که در دسته Server-side Rendering قرار دارد به شما اجازه می دهد داده ها را برای هر درخواست دوباره دریافت کنید.

متد اول: getStaticProps

برای شروع کار با data fetching از متد اول به نام getStaticProps استفاده می کنیم که داده های مورد نیاز یک صفحه را در build time (زمان ساختن وب سایت در سیستم خودتان) دریافت می کند. ابتدا به فایل index.tsx رفته و سپس از این متد برای دریافت داده هایی از سرور JSONPLACEHOLDER استفاده کنیم:

import Head from "next/head";




export default function Home({ articles }: any) {

  console.log(articles);




  return (

    <div>

      <Head>

        <title>My Next.js website</title>

        <meta name="description" content="a demo for roxo.ir/blog" />

        <meta name="keywords" content="web development, programming" />

        <link rel="icon" href="/favicon.ico" />

      </Head>




      <h1>Home Page</h1>

    </div>

  );

}




export const getStaticProps = async () => {

  const res = await fetch(`https://jsonplaceholder.typicode.com/posts?_limit=6`);




  const articles = await res.json();




  return {

    props: {

      articles,

    },

  };

};

همانطور که می بینید در ابتدا متد getStaticProps را تعریف کرده ام و درخواست خودم را به jsonplaceholder.typicode.com ارسال کرده ام. این وب سایت یک وب سایت ساده برای تست API است که داده هایی آماده را برایتان ارسال می کند تا بتوانید برنامه هایتان را با آن تست کنید. من ۶ پست را از این API گرفته ام و سپس با صدا زدن res.json می توانیم داده های JSON درون نتیجه را استخراج کنید. در نهایت باید یک شیء را برگردانید که خصوصیتی به نام props داشته باشد و داده های مورد نظرتان را به آن پاس بدهید.

انجام این کار دقیقا مانند زمانی است که prop ها را به صورت عادی به کامپوننت react پاس داده باشید. من فعلا این prop ها را گرفته و فقط log کرده ام تا ببینم آیا موفق به دریافت داده ها می شویم یا خیر. مطمئن باشید که سرور در حال اجرا است (npm run dev) و سپس به آدرس اصلی برنامه (http://localhost:3000) بروید. با باز کردن console مرورگر و سپس refresh کردن صفحه می توانید ۶ پست ارسال شده را مشاهده کنید:

[

  {

    "userId": 1,

    "id": 1,

    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",

    "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"

  },

  {

    "userId": 1,

    "id": 2,

    "title": "qui est esse",

    "body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"

  },

  {

    "userId": 1,

    "id": 3,

    "title": "ea molestias quasi exercitationem repellat qui ipsa sit aut",

    "body": "et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut"

  },

  {

    "userId": 1,

    "id": 4,

    "title": "eum et est occaecati",

    "body": "ullam et saepe reiciendis voluptatem adipisci\nsit amet autem assumenda provident rerum culpa\nquis hic commodi nesciunt rem tenetur doloremque ipsam iure\nquis sunt voluptatem rerum illo velit"

  },

  {

    "userId": 1,

    "id": 5,

    "title": "nesciunt quas odio",

    "body": "repudiandae veniam quaerat sunt sed\nalias aut fugiat sit autem sed est\nvoluptatem omnis possimus esse voluptatibus quis\nest aut tenetur dolor neque"

  },

  {

    "userId": 1,

    "id": 6,

    "title": "dolorem eum magni eos aperiam quia",

    "body": "ut aspernatur corporis harum nihil quis provident sequi\nmollitia nobis aliquid molestiae\nperspiciatis et ea nemo ab reprehenderit accusantium quas\nvoluptate dolores velit et doloremque molestiae"

  }

]

با این حساب می دانیم که همه چیز به درستی انجام شده است. در نظر داشته باشید که اگر صفحه refresh نشود این اطلاعات را مشاهده نمی کنید. چرا؟ به دلیل اینکه از متد getStaticProps استفاده می کنیم.

ساخت کامپوننت برای نمایش پست ها

ما در حال حاضر فقط پست ها را log کرده ایم اما من می خواهم آن ها را به صورت صحیح نمایش بدهم. برای این کار بهتر است یک کامپوننت جداگانه ایجاد کنیم. اولین بخش این کار این است که به پوشه styles رفته و یک فایل به نام Article.module.css ایجاد کنیم. محتوای این فایل را بدین شکل می نویسیم:

.grid {

  display: flex;

  align-items: center;

  justify-content: center;

  flex-wrap: wrap;

  max-width: 800px;

  margin-top: 3rem;

}




.card {

  margin: 1rem;

  flex-basis: 45%;

  padding: 1.5rem;

  text-align: left;

  color: inherit;

  text-decoration: none;

  border: 1px solid #eaeaea;

  border-radius: 10px;

  transition: color 0.15s ease, border-color 0.15s ease;

}




.card:hover,

.card:focus,

.card:active {

  color: #0070f3;

  border-color: #0070f3;

}




.card h3 {

  margin: 0 0 1rem 0;

  font-size: 1.5rem;

}




.card p {

  margin: 0;

  font-size: 1.25rem;

  line-height: 1.5;

}




.logo {

  height: 1em;

}




@media (max-width: 600px) {

  .grid {

    width: 100%;

    flex-direction: column;

  }

}

این استایل های مورد نیاز ما خواهد بود اما حالا نیاز به تعریف دو کامپوننت داریم:

  • یک کامپوننت برای نمایش لیستی از مقالات
  • یک کامپوننت برای نمایش هر تک مقاله (عنوان و بدنه مقاله)

من ابتدا به پوشه components رفته و دو کامپوننت جدید به نام های ArticleList.tsx و ArticleItem.tsx را تعریف می کنم. ابتدا با کامپوننت ArticleItem.tsx شروع می کنیم:

import Link from "next/link";

import articleStyles from "../styles/Article.module.css";




const ArticleItem = ({ article }: any) => {

  return (

    <Link href={`/article/${article.id}`}>

      <a className={articleStyles.card}>

        <h3>{article.title} &rarr;</h3>

        <p>{article.body}</p>

      </a>

    </Link>

  );

};




export default ArticleItem;

همانطور که می بینید من یک Link را در این بخش دارم و آدرس آن را برابر با article و سپس آیدی آن article قرار داده ام. طبیعتا قسمت id پویا است و برای هر پست تغییر می کند بنابراین در ادامه باید به این بخش برگردیم و کد های مخصوصش را بنویسیم. در مرحله بعدی یک لینک <a> و تگ های h3 و p را داریم که عنوان و محتوای مقاله را نشان می دهند. طبیعتا پست ها به صورت prop دریافت می شوند.

در مرحله بعدی به سراغ کامپوننت ArticleList.tsx رفته و می گوییم:

import articleStyles from "../styles/Article.module.css";

import ArticleItem from "./ArticleItem";




const ArticleList = ({ articles }: any) => {

  return (

    <div className={articleStyles.grid}>

      {articles.map((article: any) => (

        <ArticleItem article={article} key={article.id} />

      ))}

    </div>

  );

};




export default ArticleList;

همانطور که می بینید در این بخش یک div ساده را داریم و سپس با استفاده از تابع map بین پست های دریافت شده از API گردش کرده ایم و هر کدام را به صورت یک کامپوننت ArticleItem نمایش داده ایم. در نهایت باید به فایل index.tsx رفته و این مقالات را به این کامپوننت پاس بدهیم:

import Head from "next/head";

import ArticleList from "../components/ArticleList";




export default function Home({ articles }: any) {

  return (

    <div>

      <Head>

        <title>My Next.js website</title>

        <meta name="description" content="a demo for roxo.ir/blog" />

        <meta name="keywords" content="web development, programming" />

        <link rel="icon" href="/favicon.ico" />

      </Head>

      <ArticleList articles={articles} />

    </div>

  );

}




export const getStaticProps = async () => {

  const res = await fetch(

    `https://jsonplaceholder.typicode.com/posts?_limit=6`

  );




  const articles = await res.json();




  return {

    props: {

      articles,

    },

  };

};

دیگر خبری از console.log نیست بلکه این بار از کامپوننت ArticleList استفاده کرده ایم و articles را به صورت یک prop به آن پاس داده ایم. در حال حاضر اگر به صفحه اصلی سایت (http://localhost:3000) بروید عناوین مقالات ما را مشاهده خواهید کرد اما اگر روی مقالات کلیک کنید محتوای آن ها را نمی بینید بلکه یک خطای ۴۰۴ دریافت می کنید. چرا؟ همانطور که گفتم ما از قابلیت dynamic route یا مسیر های پویا استفاده می کنیم. یعنی هر کدام از مقالات ما یک id دارند و کلیک روی هر کدام از آن ها یک URL خاص تولید می کند مثلا http://localhost:3000/article/1 یا http://localhost:3000/article/2 و الی آخر.

next برای این مشکل نیز راه حلی دارد. ما ابتدا به پوشه pages رفته و یک پوشه به نام article را در آن ایجاد می کنیم که درون خودش یک پوشه دیگر به نام [id] داشته باشد. این نام خاص کلید حل مشکل ما است. حالا درون پوشه [id] یک فایل به نام index.tsx را ایجاد کنید. این فایل قالب کلی ما برای متن مقالات است. اولین سوال این است که چطور به id دسترسی داشته باشیم؟ طبیعتا id موجود در URL یک پارامتر ساده url است بنابراین می توانیم به شکل زیر عمل کنیم:

import { useRouter } from "next/router";




const article = () => {

  const router = useRouter();

  const { id } = router.query;




  return (

    <div>

      <p>This is article {id}</p>

    </div>

  );

};




export default article;

next یک هوک خاص به نام useRouter دارد که به ما شیء router را برمی گرداند. ما می توانیم با استفاده از این شیء به URL دسترسی داشته باشیم و با خصوصیت query مقادیر درون آن را استخراج کنیم. من از موارد موجود در URL، مقدار id را استخراج کرده ام. در نهایت این id را در صفحه نمایش داده ایم. در حال حاضر اگر به صفحات مقالات خود بروید (مثلا http://localhost:3000/article/1) می توانید id آن ها را به جای خطای ۴۰۴ مشاهده کنید. البته هنوز نمی توانیم متن مقاله را نمایش بدهیم.

متد دوم: getServerSideProps

بیایید به روش دیگری این id را دریافت کنیم. متد دوم ما که getServerSideProps نام داشت، داده ها را برای درخواست ها به طور جداگانه دریافت می کند. از طرفی یک شیء context نیز به صورت خودکار به آن پاس داده می شود که به ما اجازه دسترسی به URL را نیز می دهد بنابراین:

import { GetServerSidePropsContext } from "next/types/index";




const article = ({ article }: any) => {

  return (

    <div>

      <p>This is article {article.id}</p>

    </div>

  );

};




export const getServerSideProps = async (

  context: GetServerSidePropsContext

) => {

  const res = await fetch(

    `https://jsonplaceholder.typicode.com/posts/${context.params?.id}`

  );




  const article = await res.json();




  return {

    props: {

      article,

    },

  };

};




export default article;

همانطور که می بینید ما شیء context را گرفته ایم که به صورت خودکار پاس داده می شود و سپس با استفاده از خصوصیت params در آن به id در URL دسترسی پیدا کرده ایم. باید در نظر داشته باشید که در این حالت با هر بار کلیک روی عنوان یک پست و رفتن به صفحه آن، متد getServerSideProps دوباره اجرا می شود و داده ها دوباره دریافت می شوند اما روش دیگری نیز وجود دارد.

متد سوم: getStaticPaths

getStaticPaths به ما اجازه می دهد داده های لازم برای مسیر های پویا (dynamic routes) را تعریف کنیم. با این حساب ما می توانیم getStaticPaths و getStaticProps را ترکیب کنیم. برای این کار در فایل index.tsx به شکل زیر عمل می کنیم:

import { GetStaticPropsContext } from "next/types/index";

import Link from "next/link";




const article = ({ article }: any) => {

  return (

    <>

      <h1>{article.title}</h1>

      <p>{article.body}</p>

      <br />

      <Link href="/">Go Back</Link>

    </>

  );

};




export const getStaticProps = async (context: GetStaticPropsContext) => {

  const res = await fetch(

    `https://jsonplaceholder.typicode.com/posts/${context.params?.id}`

  );




  const article = await res.json();




  return {

    props: {

      article,

    },

  };

};




export const getStaticPaths = async () => {

  const res = await fetch(`https://jsonplaceholder.typicode.com/posts`);




  const articles = await res.json();




  const ids = articles.map((article: any) => article.id);

  const paths = ids.map((id: any) => ({ params: { id: id.toString() } }));




  return {

    paths,

    fallback: false,

  };

};




export default article;

بدنه این کامپوننت واضح بوده و نیازی به توضیح ندارد چرا که فقط عنوان و متن مقاله را نمایش داده و یک دکمه (Link) را نیز دارد که به صفحه قبل اشاره می کند. اولین متد از متد های data fetching در این بخش getStaticProps است و ما مثل همیشه در آن داده هایمان را دریافت می کنیم و به صورت props به کامپوننت پاس می دهیم. دقت کنید که در این متد فقط یک پست با id خاص را دریافت کرده ایم تا فقط یک پست به صورت prop به کامپوننت ما ارسال شود.

متد دوم getStaticPaths است که به جای دریافت تک پست، تمام پست ها را دریافت می کند. شما باید درون این متد یک شیء را return کنید که خصوصیتی به نام params داشته باشد و در آن id های خودمان را به صورت رشته ای مشخص کرده باشیم. مثلا:

{

    params: { id: "01" }

  }

این کار باید برای تک تک مسیر های پویای ما تکرار شود بنابراین ما پست ها را گرفته (متغیر articles) و شروع به گردش روی تک تک آن ها می کنیم و id هر کدام را از آن استخراج می کنیم. در واقع متغیر ids آرایه ای از id های ما است:

[

   1,  2,  3,   4,  5,  6,  7,  8,  9, 10, 11, 12,

  13, 14, 15,  16, 17, 18, 19, 20, 21, 22, 23, 24,

  25, 26, 27,  28, 29, 30, 31, 32, 33, 34, 35, 36,

  37, 38, 39,  40, 41, 42, 43, 44, 45, 46, 47, 48,

  49, 50, 51,  52, 53, 54, 55, 56, 57, 58, 59, 60,

  61, 62, 63,  64, 65, 66, 67, 68, 69, 70, 71, 72,

  73, 74, 75,  76, 77, 78, 79, 80, 81, 82, 83, 84,

  85, 86, 87,  88, 89, 90, 91, 92, 93, 94, 95, 96,

  97, 98, 99, 100

]

چرا ۱۰۰ آیدی داریم؟ به دلیل اینکه jsonplaceholder دقیقا ۱۰۰ پست را به ما می دهد و ما نیز id هایش را جدا کرده ایم. در مرحله بعدی روی تک تک این id ها گردش کرده و سپس همان شیء params را برمی گردانیم. در واقع در انتهای اجرای این دستور متغیر paths چنین مقداری را خواهد داشت:

[

  { params: { id: '1' } },

  { params: { id: '2' } },

  { params: { id: '3' } },

  { params: { id: '4' } },

  { params: { id: '5' } },

  { params: { id: '6' } },

  { params: { id: '7' } },

  { params: { id: '8' } },

  { params: { id: '9' } },

  { params: { id: '10' } },

  { params: { id: '11' } },

  { params: { id: '12' } },

  { params: { id: '13' } },

  { params: { id: '14' } },

  { ... },

  { params: { id: '90' } },

  { params: { id: '91' } },

  { params: { id: '92' } },

  { params: { id: '93' } },

  { params: { id: '94' } },

  { params: { id: '95' } },

  { params: { id: '96' } },

  { params: { id: '97' } },

  { params: { id: '98' } },

  { params: { id: '99' } },

  { params: { id: '100' } }

]

حالا دقیقا مشخص می شود که تمام مسیر های پویای ما این ۱۰۰ مورد هستند. سوالی که پیش می آید این است که اگر کاربر به آیدی ۱۰۱ برود چه می شود؟ ما می توانیم خصوصیت fallback را در شیء برگردانده شده روی false بگذاریم تا در صورتی که کاربر به صفحه ۱۰۱ رفت، یک خطای ۴۰۴ دریافت کند.

در حال حاضر اگر به مرورگر خود رفته و به صورت دستی به آدرس http://localhost:3000/article/90 بروید، واقعا پست شماره ۹۰ را مشاهده می کنید! به همین راحتی توانسته ایم مسیر های پویای خودمان را طوری تعریف کنیم که هنوز حالت static سایت را حفظ کرده باشیم. سوال من اینجاست که چطور می توانیم از این سایت به صورت ایستا (static) خروجی بگیریم؟

خروجی گرفتن از سایت به صورت ایستا (static)

فایل package.json در پروژه خود را باز کرده و به بخش scripts آن نگاهی بیندازید:

  "scripts": {

    "dev": "next dev",

    "build": "next build",

    "start": "next start",

    "lint": "next lint"

  },

دستور npm run dev باعث اجرای سرور توسعه می شد و ما تا این لحظه از آن استفاده کرده ایم. دستور npm run build به معنی تحویل سایت شما به صورت آماده است، یا به عبارتی سایت تکمیل شده و وارد حالت production می شود. در نهایت npm start نیز باعث اجرای یک سرور node می شود تا سایت در زمان قرار گیری روی سرور برای دیگران در دسترس باشد. در حالت عادی ما کل این پروژه را روی سرور قرار می دهیم و پس از اجرای اسکریپت های build و start سایت در سرور بالا می آید اما من نقشه دیگری در ذهن دارم.

من می خواهم علاوه بر تکمیل سایت، از آن به صورت ایستا (static) خروجی بگیرم. این کار با دستور next export انجام می شود بنابراین آن را به اسکریپت build اضافه می کنم:

  "scripts": {

    "dev": "next dev",

    "build": "next build && next export",

    "start": "next start",

    "lint": "next lint"

  },

پس از انجام این کار سرور خود را غیرفعال کرده و به جای آن دستور npm run build را اجرا نمایید. اجرای این دستور چند دقیقه طول می کشد اما پس از پایان آن متوجه خواهید شد که یک پوشه جدید به نام out در پروژه شما ایجاد شده است. پوشه out کل سایت شما به صورت ایستا است، یعنی چیزی جز HTML و CSS و JavaScript نیست! شما می توانید این پوشه را به تنهایی روی هر سروری قرار بدهید و همه چیز به راحتی بالا می آید. نه نیازی به تنظیمات خاصی است و نه نیازی به زبان خاصی برای اجرای آن دارید. همه چیز (تمام ۱۰۰ پست) به صورت فایل های HTML آماده هستند.

نکته: next در حالت عادی دوگانه سوز است! یعنی زمانی که next build را اجرا می کنید خودش تصمیم می گیرد که کدام صفحات را به صورت کاملا static ایجاد کند و کدام صفحات را در سمت سرور render کند. صفحاتی که نیازی به داده های پویا از سمت سرور های خارجی ندارند به صورت ایستا تولید می شوند. با این حساب هیچ نیازی به دستور next export نیست مگر اینکه مطمئن هستید تمام سایت شما ایستا است و می خواهید آن را روی یک هاست static قرار بدهید.

در نظر داشته باشید که خروجی گرفتن از سایت به صورت ایستا، برخی از قابلیت های next مانند API Routes را غیر فعال می کند که در بخش بعدی با آن ها آشنا می شویم.

تعریف مسیر های API (قابلیت API Routes)

next.js به صورت پیش فرض به شما اجازه می دهد که یک API را برای برنامه خود بسازید! در حال حاضر اگر از پوشه pages به پوشه api رفته و فایل hello.ts را باز کنید، محتویات آن را مشاهده خواهید کرد:

// Next.js API route support: https://nextjs.org/docs/api-routes/introduction

import type { NextApiRequest, NextApiResponse } from 'next'




type Data = {

  name: string

}




export default function handler(

  req: NextApiRequest,

  res: NextApiResponse<Data>

) {

  res.status(200).json({ name: 'John Doe' })

}

اگر قبلا API طراحی کرده باشید کد بالا بسیار ساده به نظر می آید. این کد یک API بسیار ساده است، دقیقا مانند API هایی که با کمک express.js می ساختیم. بیایید چند مسیر ساده را برای API خودمان تعریف کنیم.

من ابتدا فایل hello.ts را حذف می کنم چرا که نیازی به آن نداریم. در مرحله بعدی در آن یک پوشه به نام articles ایجاد می کنم که خودش حاوی فایلی به نام index.ts باشد. ما می توانیم در این فایل از هر جایی داده دریافت کنیم (مثلا به پایگاه داده خودمان کوئری ارسال کنیم) اما برای ساده نگه داشتن مطالب من می خواهم از یک فایل ساده استفاده کنم بنابراین در مسیر اصلی پروژه (پوشه next-demo) یک فایل به نام data.ts ایجاد می کنم که چند پست ساده را در خودش داشته باشد:

export const articles = [

  {

    id: "1",

    title: "GitHub introduces dark mode and auto-merge pull request",

    excerpt:

      "GitHub today announced a bunch of new features at its virtual GitHub...",

    body: "GitHub today announced a bunch of new features at its virtual GitHub Universe conference including dark mode, auto-merge pull requests, and Enterprise Server 3.0. In the past couple of years, almost all major apps have rolled out a dark theme for its users, so why not GitHub?",

  },

  {

    id: "2",

    title: "What’s multi-cloud? And why should developers care?",

    excerpt: "Most developers don’t care about multi-cloud. But they should...",

    body: "Most developers don’t care about multi-cloud. But they should. Whether developers know it or not, their companies likely already have a multi-cloud environment.    Multi-cloud is a strategy where a business selects different services from different cloud providers",

  },

  {

    id: "3",

    title: "Here is how to make your website more accessible",

    excerpt:

      "An accessible website is one that’s optimized for all people, including those with disabilities...",

    body: "There are many things to consider when setting up a website, and accessibility is one factor that can sometimes be overlooked. An accessible website is one that’s optimized for all people, including those with impaired vision or hearing, motor difficulties, or learning disabilities",

  },

  {

    id: "4",

    title: "Why open ecosystems are the future of app development",

    excerpt:

      "When app stores entered the mainstream tech culture, they exposed developers to an audience of millions...",

    body: "We can’t get enough of our mobile apps. There were 204 billion apps downloads last year, and that number is rising in 2020.  When app stores entered the mainstream tech culture, they exposed developers to an audience of millions who were keen to adopt the innovative capabilities",

  },

];




حالا به فایل api/articles/index.ts برگردید و محتویات زیر را در آن بنویسید:

import { articles } from "../../../data";

import { NextApiResponse, NextApiRequest } from "next/types";




const handler = (req: NextApiRequest, res: NextApiResponse) => {

  res.status(200).json(articles);

};




export default handler;

همانطور که می بینید من یک متد ساده را تعریف کرده ام که به صورت خودکار req و res را می گیرد و تنها کاری که می کند برگرداندن articles با کد ۲۰۰ است. حالا اگر در مرورگر به آدرس http://localhost:3000/api/articles بروید، پاسخ API به شما همان artiles است! طبیعتا این فقط یک مثال است اما به همین سادگی می توان در next.js یک API کامل را پیاده سازی کرد.

در حال حاضر فقط یک مسیر API داریم که تمام مقالات درون فایل data.ts را برمی گرداند اما اگر بخواهیم فقط یک مقاله را دریافت کنیم چطور؟ برای انجام این کار یک فایل دیگر در پوشه api/article به نام زیر ایجاد می کنیم:

[id].ts

محتویات این فایل باید به شکل زیر باشد:

import { articles } from "../../../data";

import { NextApiResponse, NextApiRequest } from "next/types";




const handler = ({ query: { id } }: NextApiRequest, res: NextApiResponse) => {

  const filtered = articles.filter(article => article.id === id);




  if (filtered.length > 0) {

    res.status(200).json(filtered[0]);

  } else {

    res.status(404).json({ message: `article with id of ${id} was not found` });

  }

};




export default handler;

همانطور که در مثال قبلی دیدیم شیء req و res به صورت خودکار به تابع ما پاس داده می شوند. درون شیء req یک خصوصیت به نام query وجود دارد که به ما اجازه دسترسی به محتوای URL را می دهد اما من به طور مستقیم آن را destructure کرده ام تا به شکل بالا به آن دسترسی داشته باشیم. در مرحله بعدی از بین مقالاتی که داریم به دنبال مقاله ای گشته ایم که id آن برابر با id ارسال شده باشد. برای این کار از متد filter استفاده کرده ایم که یک آرایه برمی گرداند.

در مرحله بعدی در یک شرط if بررسی کرده ایم که آیا طول آرایه برگردانده شده بیشتر از صفر است؟ یعنی آیا چنین مقاله ای پیدا شده است؟ اگر اینطور بود آن مقاله را برمی گردانیم و در غیر این صورت پیامی را ارسال می کنیم که چنین مقاله ای وجود ندارد. برای تست این کد در مرورگر می توانید به آدرسی شبیه به http://localhost:3000/api/articles/1 بروید.

استفاده از API در کامپوننت ها

ما تا این لحظه یک API بسیار کوچک را ساخته ایم که یک یا چند مقاله را برایمان برمی گرداند. در اینجا سوالی پیش می آید: آیا می توانیم از این API در کامپوننت هایمان استفاده کنیم؟ پاسخ قطعا مثبت است. برای انجام این کار ابتدا در پوشه اصلی پروژه (next-demo) یک پوشه به نام config ایجاد کنید که درون خود فایلی به نام index.ts داشته باشد. ما می خواهیم درون این فایل مشخص کنیم که در حالت توسعه (development) هستیم یا در حالت بهره برداری (production) بنابراین:

const dev = process.env.NODE_ENV !== "production";

export const server = dev ? "http://localhost:3000" : "YOUR WEBSITE HERE";

در کد اول گفته ایم اگر NODE_ENV برابر با production نبود، مقدار متغیر dev برابر با true باشد. حالا در خط دوم از این متغیر استفاده کرده ایم و گفته ایم اگر dev صحیح بود (یعنی در حالت توسعه بودیم) مقدار متغیر سرور برابر با http://localhost:3000 است در غیر این صورت نیز برابر با YOUR WEBSITE HERE خواهد بود. طبیعتا شما باید به جای YOUR WEBSITE HERE آدرس واقعی سایت خود را وارد کنید.

احتمالا می پرسید چرا باید آدرس سایت را بدین شکل مشخص کنیم؟ به دلیل اینکه ما می خواهیم از API خودمان استفاده کنیم و در API ها باید آدرس کامل سایت را داشته باشیم. به pages/index.tsx بروید و این بار در getStaticProps به آدرس API خودمان درخواست ارسال می کنیم:

import Head from "next/head";

import { server } from "../config";

import ArticleList from "../components/ArticleList";




export default function Home({ articles }: any) {

  return (

    <div>

      <Head>

        <title>My Next.js website</title>

        <meta name="description" content="a demo for roxo.ir/blog" />

        <meta name="keywords" content="web development, programming" />

        <link rel="icon" href="/favicon.ico" />

      </Head>

      <ArticleList articles={articles} />

    </div>

  );

}




export const getStaticProps = async () => {

  const res = await fetch(`${server}/api/articles`);

  const articles = await res.json();




  return {

    props: {

      articles,

    },

  };

};

همانطور که می بینید آدرس ارسالی من به api/articles روی سرور خودمان بوده است. در مرحله بعدی به فایل pages/article/[id]/index.tsx می رویم تا آدرس API در این فایل را نیز تصحیح کنیم:

import { GetStaticPropsContext } from "next/types/index";

import { server } from "../../../config";

import Link from "next/link";




const article = ({ article }: any) => {

  return (

    <>

      <h1>{article.title}</h1>

      <p>{article.body}</p>

      <br />

      <Link href="/">Go Back</Link>

    </>

  );

};




export const getStaticProps = async (context: GetStaticPropsContext) => {

  const res = await fetch(`${server}/api/articles/${context.params?.id}`);




  const article = await res.json();




  return {

    props: {

      article,

    },

  };

};




export const getStaticPaths = async () => {

  const res = await fetch(`${server}/api/articles`);




  const articles = await res.json();




  const ids = articles.map((article: any) => article.id);

  const paths = ids.map((id: any) => ({ params: { id: id.toString() } }));




  return {

    paths,

    fallback: false,

  };

};




export default article;

همانطور که می بینید این فایل به جز تغییر آدرس برای fetch تغییرات زیادی نداشته است.

بنابراین شما می توانید تیمی داشته باشید که از یک سمت مسئول تولید بخش front-end سایت و از طرف دیگر مشغول توسعه back-end باشند. یکی از مزیت های اصلی پروژه های Next.js همین است که به شما اجازه می دهد تمام بخش های مختلف یک پروژه را در یک قسمت داشته باشید. از طرف دیگر تمام بخش های پروژه از قبل با فایل های مختلف جدا شده اند بنابراین نظم خاصی به پروژه هایتان می دهند. از مزایای مهم و اصلی Next.js نیز می توان به قابلیت Server-side Rendering و Static Site Generation اشاره کرد که با هر دو در این مقاله آشنا شدیم.

توجه داشته باشید که هنوز مطالب زیادی برای آموزش Next.js وجود دارد و شما می توانید با مراجعه به وب سایت رسمی آن، با موضوعات خاص تر و جزئی تر آشنا شوید.

نویسنده شوید
دیدگاه‌های شما (5 دیدگاه)

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

hoda
29 مرداد 1402
عالی بود...ممنون

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

روکسو
31 مرداد 1402
خوشحال هستیم که همراه شماییم.

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

محمد
20 شهریور 1401
سلام و خسته نباشد توی ریدایرکت، source کارش چیه دقیقا ؟

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

رضا
03 مرداد 1401
بسیار عالی لطف کردید

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

روکسو
11 مرداد 1401
تشکر از حضور شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

name
29 اسفند 1400
اگر درباره load کردن تصاویر در Nextjs هم آموزشی بگذارید خیلی خوبه

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

روکسو
12 اردیبهشت 1401
ممنون از نظر شما. حتما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

رشید
19 اسفند 1400
از آموزش خوبتون متشکرم آقای زوارمی

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

روکسو
12 اردیبهشت 1401
تشکر از همراهی شما با سایت روکسو

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.