آموزش فریمورک نکست جیاس (Next.js Framework)

با سلام خدمت کاربران همیشگی آکادمی راهکارینو، در این مقاله قصد داریم فریم ورک محبوب نکست جی اس را معرفی کرده و بطور مقدماتی آموزش دهیم.
فریم ورک Next.js چیست:
نکست جی اس یک فریم ورک برای توسعه کتابخانه ری اکت است که به منظور پیاده سازی رندر سمت سرور (SSR: Server-Side-Rendering) و تولید سایت استاتیک (SSG: Static-Site-Generation) بکار می رود. این مقاله در ژانویه 2023 (بهمن 1401) بروزرسانی شده است.
نسخه
نحوه رندر شدن پیش فرض اپلیکیشن ریکت:
اپ ریکت بطور پیش فرض بصورت CSR یا Client-side-rendering رندر می شود. بدین معنی که وقتی کاربر به سرور ریکوئست می دهد و می خواهید مثلا محتوای یک صفحه از وب اپلیکیشن را مشاهده کند سرور فقط یک فایل html را بهمراه یک فایل باندل js به سمت مرورگر کاربر (کلاینت ساید) ارسال می کند.
رندر کردن محتوای صفحات در این حالت وظیفه مرورگر می باشد. پس باید قابلیت javascript در تنظیمات مرورگر کلاینت فعال باشد. وگرنه لود اپ ریکت با خطا روبرو خواهد شد.
معایب رندر شدن صفحات سمت کلاینت (CSR):
اگر رندر شدن اسکریپت های اپلیکیشن سمت کاربر انجام شود، یعنی CSR، ربات های موتور جستجوی گوگل نیز بسختی می توانند محتوای صفحه را مشاهده کند، مثلا تگ های Heading و تصاویر و سایر المان ها را در مرحله اول نمی بیند و ابتدا باید فایل js را اجرا کنند. در نتیجه صفحات دیرتر ایندکس خواهند شد. پس عدم کسب رتبه مناسب در نتایج سرچ موتورهای جستجو از دیگر معایب روش CSR می باشد.
تصویر زیر را در این زمینه مشاهده کنید:
سورس کد یک اپ ریکت را بصورت پیش فرض میبینید که شامل یک لوگوی ریکت است بعلاوه یک متن و یک لینک در زیر آن. اما اگر source-code این صفحه را در مرورگر مشاهده کنید تصویر زیر را خواهیم داشت:
مشاهده می کنید که در تگ body فقط و فقط یک تگ div با آیدی root وجود دارد و هیچکدام از تگ های مذکور رندر نشده اند.
برای رفع این مشکل باید روش رندر شدن محتوای صفحه بصورت SSR یا Server-side-rendering باشد. بدین منظور می خواهیم از فریم ورک NextJS استفاده کنیم.
اگر به سایت Reactjs.org مراجعه کنید، و سورس صفحه را با زدن کلیدهای ctrl+u مشاهده کنید، متوجه می شوید که اسکریپت های سایت مرجع ریکت نیز در سمت سرور رندر می شوند.
چگونه بفهمیم یک وب اپلیکیشن بصورت SSR رندر می شود یا CSR؟
به دو روش می توانید بفهمید که وب سایت ریکت از SSR استفاده می کند:
- سرچ یک عبارت موجود در خروجی سایت (راست کلیک و انتخاب گزینه View source یا زدن کلیدهای ترکیبی ctrl+u). اگر عبارت مورد جستجو در سورس صفحه موجود باشد رندر بصورت SSR وگرنه CSR می باشد.
- اگر به منوی تنظیمات مرورگر خود بروید و قابلیت اجرای اسکریپت های javascript را غیر فعال کنید، سایت ریکت بدرستی و بدون مشکل لود می شود. بنابراین می توان فهمید که محتوای این سایت در سمت سرور انجام شده است و نیازی به رندر شدن اسکریپت ها در سمت کلاینت و مرورگر کاربر وجود ندارد.
چرا باید گاهی اوقات از CSR استفاده شود؟
فرض کنید شما از ریکت برای توسعه داشبورد مدیریت در وب اپلیکیشن خود استفاده کرده اید. مسلما نمی خواهید محتوای داشبورد مدیریت در گوگل ایندکس شود و در نتیج جستجو بیایند. پس یکی از کاربردهای روش CSR (روش رندر پیش فرض اپ های ریکتی) در داشبورد کاربران و مدیریت وب اپلیکیشن ها می باشد.
بنابراین می توان جمع بندی کرد که:
- اگر نیاز است محتوای صفحه وب اپلیکیشن شما در گوگل ایندکس شود و کاربران بتوانند در نتایج جستجوی گوگل به محتوای آن دسترسی داشته باشند روش SSR توصیه می شود.
- و در صورتی که نمی خواهید محتوای صفحات سایت تان در گوگل ایندکس نشوند CSR.
نکته: وب اپلیکیشن هایی که بصورت SSR رندر می شوند گاها بنام ایزومورفیک (Isomorphic) نیز شناخته می شوند زیرا هم می توانند سمت سرور رندر شوند (Server-side-rendering) و هم سمت کلاینت (Client-side-rendering).
مزایای فریم ورک نکست جی اس:
- Routing ساده صفحات
- تعریف Route های api سرور
- SSG یا تولید سایت استاتیک
- Deployment آسان
ایجاد اولین پروژه با Next.JS:
برای شروع کار با فریم ورک Next و ایجاد یک پروژه، می توان از یکی از دستورات yarn یا npm بصورت زیر استفاده کرد:
npx create-next-app my-app-name # OR yarn create next-app my-app-name
یا اگر می خواهید از فریم ورک TailwindCSS برای رابط کاربری (UI) اپلیکیشن خود استفاده کنید می توانید دستور زیر را اجرا کنید:
npx create-next-app -e with-tailwindcss my-app-name
دستورات فوق یک پروژه نکست جی اس را با تمام کانفیگ های لازم و فایل ها و فولدرهای مربوطه ایجاد می کند.
و در صورتی که می خواهید در پروژه نکست خود به جای زبان برنامه نویسی javascript از typescript استفاده کنید، باید یکی از دستورات زیر را اجرا کنید:
npx create-next-app@latest --typescript # or yarn create next-app --typescript # or pnpm create next-app --typescript
نسخه فعلی نکست جی اس (در زمان نگارش این مقاله) 13 می باشد.
برای اجرای پروژه دستور زیر را اجرا کنید:
cd your-app-name npm run dev
با اجرای این دستور یک صفحه خوش آمدگویی یا Welcome page مطابق تصویر زیر خواهید دید:
صفحات و Routing در نکست جی اس:
در فریم ورک nextjs برای مدیریت مسیرها یا route های اپلیکیشن، نیازی به نصب پکیج جانبی مانند react-router نداریم. در واقع پیاده سازی مسیرهای موردنظر در اپلیکیشن های نکست جی اس بسیار ساده و سریع است. وقتی با دستور create-next-app یک پروژه Nextjs را راه اندازی می کنید بصورت پیش فرض فولدری بنام pages خواهید داشت.
از پوشه pages برای تعریف مسیرها و route های اپلیکیشن استفاده می شود. بدین صورت که هر کامپوننت ری اکتی که در این پوشه تعریف شود به عنوان یک مسیر مجزا در برنامه شناخته خواهد شد.
بعنوان مثال فرض کنید کامپوننت های زیر را در این پوشه تعریف می کنیم:
- index.js
- about.js
- blog.js
بنابراین مسیرهای زیر را خواهیم داشت:
- The index page localhost/index
- The about page localhost/about
- The blog page localhost/blog
مشاهده می کنید که پیاده سازی مسیرها در نکست بسیار ساده است. همچنین اگر مسیری را وارد کنیم که کامپوننت آن را در فولدر pages تعریف نکرده باشیم، مانند localhost/home، بطور اتوماتیک پیغام خطای 404 را مشاهده خواهیم کرد و نیازی به انجام تنظیمات خاصی نیست.
برای مثال کامپوننت AboutPage می تواند به شکل زیر تعریف شود:
function AboutPage() { return ( <div> <h1>About</h1> </div> ) } export default AboutPage
مشاهده می کنید که یک فانکشنال کامپوننت عادی ری اکت است و کد ویژه ای ندارد.
مسیرهای تودرتو یا Nested Routes:
اما برای اینکه بتوانیم مسیرهای تو در تو مانند localhost/blog/contact را تعریف کنیم چه کاری باید انجام داد؟
برای اینکار فقط کافیست فولدرهای تو در تو ایجاد کنیم. مانند pages/blog و داخل فولدر blog یک کامپوننت بنام contact.js را تعریف کنیم. در این صورت، کامپوننت Contact در مسیر localhost/blog/contact قابل دسترس خواهد بود.
نکته: اگر نام فایل را index.js تعریف کنیم، نکست جی اس این کامپوننت را بعنوان روت مسیر در نظر می گیرد. برای روشن شدن قضیه به این مثال توجه کنید. فرض کنید فایل index را در مسیر pages/blog/index.js تعریف کرده ایم. در اینصورت کامپوننت index.js در مسیر pages/blog لود خواهد شد.
مسیرهای پویا یا Dynamic Routes:
اما در مورد مسیرهای داینامیک قضیه فرق دارد. منظور مسیرهایی مانند موارد زیر است:
localhost/blog/my-first-blog
localhost/blog/my-second-blog-post
به منظور تعریف مسیرهای داینامیک در نکست جی اس، می توان از براکت به شکل pages/blog/[slug].js استفاده کرد. در بخش زیر نمونه ای از کد slug را مشاهده می کنید:
import { useRouter } from 'next/router' function PostPage() { const router = useRouter() return ( <div> <h1>My post: {router.query.slug}</h1> </div> ) } export default PostPage
مشاهده می کنید که با استفاده از هوک useRouter می توان slug مربوط به مسیر جاری را دریافت کرد. با دستور router.query.slug
مثال فوق یک کد اولیه و دمو می باشد. در یک پروزه واقعی، در فایل های [slug] یا کلا مسیرهای داینامیک، با ارسال درخواست به api داده مربوط به پست جاری را دریافت خواهیم کرد.
کامپوننت Link در نکست:
اکنون که توانستیم اولین مسیر یا Route پروژه نکست خود را ایجاد کنیم، شاید این سوال برایتان پیش آمده باشد که چگونه می توان بین مسیرهای مختلف جابجا شد؟ با استفاده از کامپوننت Link که از next/link ایمپورت می شود می توان صفحات را به یکدیگر لینک کرد. به مثال زیر دقت کنید:
import Link from 'next/link' export default function Home() { return ( <div> <h1>Home</h1> <Link href="about">About</Link> </div> ) }
در کد فوق، از کامپوننت Home یک لینک به کامپوننت About ایجاد کرده ایم.
و در مسیر about اگر خواستیم یک لینک به صفحه اصلی یا Home تعریف کنیم، بصورت زیر دستور Link را اجرا می کنیم:
<Link href="/">Home</Link>
نکته: کامپوننت Link استایل نمی گیرد. در واقع نمی توان از استایل های CSS بطور مستقیم در Link استفاده کرد. برای استایل دادن به لینک در نکست، باید مثلا یک span به شکل زیر در داخل link تعریف کرد و به آن استایل دلخواه داد:
<Link href='/about'> <span className="text-blue-500">About this project</span> </Link>
فرض کنید یک آرایه ای از آبچکت ها داریم بنام users که شامل آیدی و یوزرنیم کاربران است:
const users = [ { id: 1, username: "Ehsan", }, { id: 2, username: "Rahkarino", }, ];
وقتی می خواهیم روی آرایه users لوپ بزنیم، و اطلاعات هر کاربر را در یک li مجزا نمایش دهیم، می توان به شکل زیر از Template Literal (یا Template String) استفاده کنیم:
<ul> {users.map((user) => { return ( <li key={user.id}> <Link to={`/users/${user.id}`}>{user.username}</Link> </li> ); })} </ul>
در کد فوق، در هر li یک کامپوننت لینک داریم که مقدار to آن بصورت template literal تعریف شده است اما روش بهینه تر برای تعریف آدرس Link به شکل زیر است:
<Link to={{ pathname: "/users/[id]", query: { id: user.id }, }} > {user.username} </Link>
در کد بالا، با استفاده از یک روش بهینه تر و خواناتر، مقدار pathname و نیز query را در قالب یک آبجکت تعریف کرده ایم.
سورس کد کامل کامپوننت UsersList به شکل زیر است:
const UsersList = () => { const users = [ { id: 1, username: "Ehsan", }, { id: 2, username: "Rahkarino", }, ]; return ( <ul> {users.map((user) => { return ( <li key={user.id}> <Link to={{ pathname: "/users/[id]", query: { id: user.id }, }} > {user.username} </Link> </li> ); })} </ul> ); }; export default UsersList;
برای اینکه بتوانیم برای هر یوزر، یک route مجزا و اختصاصی داشته باشیم، کافیست یک فایل بنام [id] داشته باشیم که فرمت آن jsx (یا tsx) باشد. برای نمایش آیدی یوزر، می توان از useRouter به شکل زیر استفاده کرد:
import { useRouter } from "next/router"; const UserDetail = () => { const router = useRouter(); return <div className="user-info">User-ID: {router.query.id}</div>; }; export default UserDetail;
برای تغیر مسیر بصورت دستی، می توان متد push را از آبجکت router صدا زد. در کد زیر، با کلیک روی یک دکمه، مسیر جاری برنامه به /users/1 تغییر پیدا می کند:
const UserDetail = () => { const router = useRouter(); const changeRoute = () => { // router.push('/users/1'); router.push({ pathname: "/users/[id]", query: { id: 1 }, }); }; return ( <div className="user-info"> User-ID: {router.query.id} <button onClick={changeRoute}>Change Route</button> </div> ); };
ریدایرکت مسیر یا Route Redirect:
اکنون اگر بخواهید در اپلیکیشن نکست به یک مسیر دیگر منتقل شوید باید چه کار کنید؟
برای redirect شدن بین مسیرهای مختلف بصورت دستی، می توان از هوک useRouter استفاده کرد. مطابق کد زیر:
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> ) }
در این مثال با استفاده از دستور router.push پس از کلیک روی دکمه، به صفحه اصلی اپلیکیشن منتقل خواهیم شد.
تعریف کامپوننت ها در فریم ورک نکست:
در یک پروژه واقعی ریکت، معمولا نیاز به تعریف کامپوننت هایی مانند Navbar داریم که نباید مسیر مجزایی برای آنها تعریف شود. یعنی مسلما کاربران برای مشاهده منوی راهبری وارد مسیر localhost/navbar نخواهند شد!
برای اینکه اینگونه کامپوننت ها را در پروژه Nextjs تعریف کنیم می توان یک فولدر بنام components در کنار فولدر pages در روت پروژه تعریف کرد. این نام گذاری یک استاندارد است و اغلب توسعه دهنده گان آن را رعایت می کنند. اما الزامی به رعایت آن نمی باشد.
بعنوان مثال می توان کامپوننت Navbar را در مسیر /components/Navbar.js تعریف کرد. در اینصورت، این کامپوننت در تمام اپلیکیشن قابل دسترس خواهد بود.
کامپوننت Head:
به منظور تعریف تگ های متا و title برای صفحات اپلیکیشن next می توان از کامپوننت Head استفاده کرد. در مثال زیر، کامپوننت Head را در Layout برنامه تعریف کرده ایم:
// 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' }
ایجاد صفحه 404 سفارشی:
در اپلیکیشن نکست این امکان وجود دارد که صفحه 404 سفارشی خود را طراحی کنید. ممکن است بخواهید طرح پیش فرض این صفحه را طبق طراحی قالب اپلیکیشن خود تغییر دهید. به این منظور یک فایل بنام 404.js در فولدر pages بسازید.
404.js
بعنوان مثال می توانید کد زیر را برای این فایل تعریف کنید:
// 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
دریافت اطلاعات سمت سرور (Server-Side Data Fetching):
بجای دریافت داده ها در سمت کلاینت، فریم ورک نکست این امکان را فراهم می کند تا داده ها را در سمت سرور دریافت کند و صفحات آماده و کامل را به سمت کلاینت ارسال کند.
به منظور دریافت اطلاعات در سرور، دو روش کلی داریم:
- دریافت اطلاعات از سرور با هر درخواست
- دریافت اطلاعات در زمان Build پروژه (استاتیک)
دریافت اطلاعات با هر درخواست (تابع getServerSideProps):
برای اینکه با هر بار درخواست از سرور، اطلاعات از سرور دریافت شود، باید از تابع getServerSideProps در نکست استفاده شود. این تابع را می توانید در انتهای کامپوننت تعریف کنید. بعنوان مثال:
export async function getServerSideProps() { const res = await fetch(`http://server-name/api/items`) const items = await res.json() return { props: {items}, } }
وقتی فریم ورک Nextjs به تابع getServerSideProps می رسد مقدار props کامپوننت جاری را با مقدار دریافتی از این تابع پر می کند. در مثال بالا، مقدار items بعنوان props تعریف می شود و در کامپوننت می توان از آن به شکل props.items استفاده کرد.
دریافت اطلاعات در زمان Build پروژه (تابع getStaticProps):
اما در صورتی که می خواهید اطلاعات یکبار و آن هم در زمان build پروژه از سرور دریافت شوند، باید از تابع getStaticProps استفاده شود. بعنوان مثال:
export async function getStaticProps() { const res = await fetch('http://server-name/api/items') const items = await res.json() return { props: {items}, } }
همانطور که مشاهده کردید، بدنه توابع getStaticProps و getServerSideProps با یکدیگر تفاوتی ندارد و فقط نام تابع متفاوت است.
نکته مهم: اگر می خواهید از تابع getStaticProps در مسیرهای داینامیک (مانند /posts/my-first-post) استفاده کنید، یعنی در فایل posts/[slug].js ، باید علاوه بر این تابع، یک تابع دیگری بنام getStaticPaths نیز تعریف کنید. در غیر اینصورت برنامه شما با خطا مواجه می شود.
برای مثال می توان دو تابع getStaticPaths و getStaticProps را بصورت زیر در انتهای کامپوننت موردنظر تعریف کرد:
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] } } }
تابع getStaticPaths به این دلیل باید تعریف شود که فریم ورک نکست باید لیست تمام route های داینامیک را در زمان build پروژه شناسایی کند.
بهینه سازی تصاویر در نکست:
در فریم ورک Next کامپوننتی داریم بنام Image که بطور خودکار می تواند تصاویر را بهینه سازی یا optimize کند. Image از next/image ایمپورت می شود و مانند تگ img می توان با آن رفتار کرد و اتریبیوت های img را دارد. بعنوان مثال:
import Image from 'next/image'
<Image src="/image.png" alt="Picture of the author" width={500} height={500} />
اگر می خواهید درباره کامپوننت Image در Nextjs بیشتر بدانید، به این مقاله مراجعه کنید.
خواندن محتویات فایل json در نکست:
در این بخش می خواهیم نحوه خواندن دیتای داخل یک فایل json را در فریم ورک nextjs آموزش دهیم. برای شروع کار، ابتدا یک فولدر بنام data ایجاد کرده و درون آن یک فایل بنام products.json بسازید و دیتای زیر را در آن تعریف کنید:
{ "products": [ { "id": "1", "title": "product-1" }, { "id": "2", "title": "product-2" }, { "id": "3", "title": "product-3" } ] }
برای خواندن محتوای یک فایل باید از دستورات node استفاده کنیم. ابتدا باید ماژول fs (مخفف file system) را به شکل زیر ایمپورت کنیم. همچنین باید آبجکت path را به منظور آدرس دهی فایل، ایمپورت کنیم:
import fs from 'fs/promises'; import path from "path";
تمام کدهای لازم برای خواندن یک فایل باید سمت سرور اجرا شوند. پس می توان در متد getStaticProps آنها را تعریف کرد.
توسط دستور زیر می توان مسیری که فایل products.json قرار دارد را به برنامه معرفی کنیم:
const filePath = path.join(process.cwd(), "data", "products.json");
در کد بالا، توسط آبجکت path توانستیم مسیر فایل موردنظر را داخل متغیر filePath بریزیم. cwd مخفف current working directory می باشد که بیانگر فولدر جاری پروژه است. آرگومان دوم فولدری که فایل در آن قرار دارد و آرگومان سوم هم نام فایل بهمراه پسوند آن می باشد.
سپس توسط دو دستور زیر، محتوای فایل فوق را می خوانیم و فرمت json را parse می کنیم:
const jsonData = await fs.readfile(filePath); const data = JSON.parse(jsonData);
در نهایت در return فانکشن، عبارت زیر را بعنوان props تعریف می کنیم:
return { props: { products: data.products } }
سورس کد متد getStaticProps به شکل زیر است:
export async function getStaticProps() { const filePath = path.join(process.cwd(), "data", "products.json"); const jsonData = await fs.readfile(filePath); const data = JSON.parse(jsonData); return { props: { products: data.products } } }
به همین راحتی توانستیم دیتای درج شده در یک فایل json را در فریم ورک نکست جی اس بخوانیم و از آن بصورت props در کامپوننت خود استفاده کنیم.
جمع بندی:
در این مقاله آموزشی فریم ورک پر طرفدار NextJS را مورد بررسی قرار دادیم و توانستیم یک اپلیکیشن ساده ریکتی تولید کنیم که صفحات آن در سمت سرور (Server-Side) رندر می شوند. شما باید با توجه به نیاز پروژه خود، تصمیم بگیرید که آیا می خواهید محتوای صفحات وب اپلیکیشن تان در موتور جستجوی گوگل ایندکس شوند یا خیر. در صورتیکه بعنوان مثال یک داشبورد ادمین را توسط ریکت پیاده سازی می کنید نیازی نیست صفحات آن به صورت SSR رندر شوند. اما اگر مثلا می خواهید یک فروشگاه اینترنتی را توسط ReactJS طراحی کنید اطمینان حاصل کنید که صفحات فروشگاه شما بصورت SSR رندر شوند. تا هر چه سریع تر توسط گوگل پیمایش و ایندکس (Crawl & Index) شوند و کاربران بتوانند با سرچ کلمات کلیدی هدف شما وارد سایت تان شوند.
در مقاله “آموزش پیشرفته فریم ورک نکست جی اس” مباحث پیشرفته و تکمیلی فریم ورک Next.js آموزش داده خواهد شد. همراه ما باشید.
امیدوارم از این پست استفاده لازم را برده باشید. در صورتی که سوال یا پیسنهادی دارید لطفا به یکی از روش های زیر با آکادمی راهکارینو در تماس باشید:
- فرم تماس با ما
- فرم ارسال دیدگاه پایین مقاله
- ارسال ایمیل به ehsansafari@hotmail.com
- دایرکت به اینستاگرام rahkarino
- ارسال پیام به تلگرام rahkarino@
مطالب زیر را حتما مطالعه کنید
2 دیدگاه
به گفتگوی ما بپیوندید و دیدگاه خود را با ما در میان بگذارید.
خیلی خوب بود
ممنون از شما
لطفا از Next آموزش های بیشتری رو بزارید
سلام.
خواهش می کنم. خوشحالم که مقاله مفید بوده براتون.
بله حتما. آموزش های جدید در حال بروزرسانی هستند.