نسخه Next.js 14 ویژگی جدیدی به نام partial pre-rendering را معرفی کرده است. این ویژگی به توسعهدهندگان کمک میکند تا بتوانند این موضوع، که کدام قسمت از یک صفحه pre-render شده و یا از ابتدا رندر شود را کنترل کنند. partial pre-rendering از React Suspense API استفاده میکند، بنابراین اگر با React آشنا باشیم درک و استفاده از آن ساده خواهد بود.
ویژگی partial pre-rendering از ترکیبی از پردازش استاتیک، به ویژه incremental static regeneration (ISR) و server-side processing (SSR) استفاده میکند. استفاده از ISR در Next.js پیش رندر صفحات حاوی دادههای داینامیک را در طول زمان ساخت، فعال میکند. به این ترتیب این صفحات در صورت نیاز، به تدریج در پسزمینه رندر میشوند و تجربه کاربری کارآمد و پویایی را ارائه میدهند.
در این مقاله قصد داریم تا نحوه عملکرد ویژگی partial pre-rendering و استفاده از آن در برنامههای Next.js را بررسی کنیم. البته باید این موضوع را به خاطر داشته باشیم با توجه به این که این ویژگی هنوز در مرحله آزمایشی میباشد، بنابراین استفاده از آن در محیط واقعی توصیه نمیشود.
partial pre-rendering در نسخه Next.js 14 به توسعهدهندگان اجازه میدهد تا مشخص کنند کدام بخش یا بخشهای صفحه باید از قبل رندر شوند. بنابراین توسعهدهندگان میتوانند کنترل بیشتری بر فرآیند بهینهسازی داشته باشند. این ویژگی تا زمانی که دادهها آماده و در دسترس باشند از Concurrent API و Suspense مربوط به React برای «تعلیق» یا «مکث» عمل rendering استفاده میکند، که این موضوع منجر به عملکرد سریعتر و بهینهتر میشود.
React Suspense API به کامپوننتها اجازه میدهد تا در هنگام انتظار برای دادهها، معمولاً در حین دریافت آنها به صورت asynchronous، رندر را به حالت تعلیق درآورند یا متوقف کنند.
Suspense API یک رابط کاربری fallback فراهم میکند که هنگام لود دادهها ظاهر میشود. این رابط کاربری همراه با سایر محتویات استاتیک در صفحه لود شده و نمایش داده میشود. سپس تا زمانی که دریافت asynchronous دادهها کامل شود، رابط کاربری fallback قابل مشاهده باقی میماند. در نهایت پس از آماده شدن دادهها، با آنها جایگزین میگردد.
رابط کاربری fallback معمولاً loaderای است که میخواهیم به کاربران خود نشان دهیم تا بدانند محتوا در حال لود شدن است. به این ترتیب آنها میتوانند تا لود شدن کامل محتوا، قسمتهای دیگر صفحه که قبلاً لود شده و یا از قبل رندر شدهاند را مشاهده کنند.
برای استفاده از partial pre-rendering، ابتدا قسمتهایی را در برنامه خود تعیین میکنیم که در آن عملیات asynchronous انجام میشود. این قسمت ایدهآلی است که میخواهیم Suspense API را در آن اعمال کنیم. برای مثال، کامپوننتی که دادهها را بهصورت asynchronous دریافت میکند، میتواند داخل کامپوننت <Suspense>
قرار بگیرد. این کار نشان میدهد که آن قسمت باید تا زمانی که دادهها آماده و در دسترس باشد، به تعویق بیفتد و یا اینکه به حالت تعلیق درآید.
import React, { Suspense } from "react" const App = () => ( <Suspense fallback={<Loader />}> ....your component </Suspense> )
کامپوننتهایی که درون کامپوننت Suspense
قرار گرفتهاند، آنهایی هستند که معلق خواهند بود. برای استفاده از partial pre-rendering، اصلاً نیازی به تغییر کد خود نداریم، فقط باید بخش یا صفحه مورد نظر خود را داخل کامپوننت Suspense قرار دهیم. Next.js میداند که کدام قسمتها را به صورت استاتیک یا داینامیک رندر کند.
برای استفاده از ویژگی partial pre-rendering در Next.js، آخرین نسخه Canary را با استفاده از دستورات زیر نصب میکنیم:
/* using npm */ npm install next@canary /* using yarn */ yarn add next@canary
سپس، در فایل next.config.js
خود، پیکربندی زیر را اضافه میکنیم:
experimental: { ppr: true, },
فایل next.config.js
ما باید به شکل زیر باشد:
/** @type {import('next').NextConfig} */ const nextConfig = { experimental: { ppr: true, }, } module.exports = nextConfig
اکنون میتوانیم از Suspense API استفاده کنیم. به عنوان مثال:
async function Posts() { const data = await fetch(`https://jsonplaceholder.typicode.com/posts`, { cache: 'no-store', }) const posts = await data.json() return ( <> <h2>All Posts</h2> {posts.slice(0, 7).map((post) => ( <div key={post.id}> <h4>Title: {post.title}</h4> <p>Content: {post.body}</p> </div> ))} </> ) } export default function Home() { return ( <main className="flex min-h-screen flex-col items-center justify-between p-24"> <div> <h1>Partial Pre-Rendering</h1> <p> Morbi eu ullamcorper urna, a condimentum massa. In fermentum ante non turpis cursus fringilla. Praesent neque eros, gravida vel ante sed, vehicula elementum orci. Sed eu ipsum eget enim mattis mollis. Morbi eu ullamcorper urna, a condimentum massa. In fermentum ante non turpis cursus fringilla. Praesent neque eros, gravida vel ante sed, vehicula elementum orci. Sed eu ipsum eget enim mattis mollis. </p> </div> <Posts /> </main> ) }
در کد بالا، یک صفحه ساده برای دریافت و رندر کردن برخی از پستها داریم. با استفاده از partial pre-rendering میتوانیم محتوای پستها را تا زمانی که دادهها در دسترس قرار بگیرند به تعویق بیاندازیم. در مدت زمانی که طول میکشد تا دادهها دریافت شوند، رابط کاربری fallback که ما مشخص میکنیم با سایر محتویات استاتیک رندر میشود:
import { Suspense } from 'react' function LoadingPosts() { const shimmer = `relative overflow-hidden before:absolute before:inset-0 before:-translate-x-full before:animate-[shimmer_1.5s_infinite] before:bg-gradient-to-r before:from-transparent before:via-white/10 before:to-transparent` return ( <div className="col-span-4 space-y-4 lg:col-span-1 min-h-screen w-full mt-20"> <div className={`relative h-[167px] rounded-xl bg-gray-900 ${shimmer}`} /> <div className="h-4 w-full rounded-lg bg-gray-900" /> <div className="h-6 w-1/3 rounded-lg bg-gray-900" /> <div className="h-4 w-full rounded-lg bg-gray-900" /> <div className="h-4 w-4/6 rounded-lg bg-gray-900" /> </div> ) } async function Posts() { const data = await fetch(`https://jsonplaceholder.typicode.com/posts`, { cache: 'no-store', }) const posts = await data.json() return ( <> <h2>All Posts</h2> {posts.slice(0, 7).map((post) => ( <div key={post.id}> <h4>Title: {post.title}</h4> <p>Content: {post.body}</p> </div> ))} </> ) } export default function Home() { return ( <main className="flex min-h-screen flex-col items-center justify-between p-24"> <div> <h1>Partial Pre-Rendering</h1> <p> Morbi eu ullamcorper urna, a condimentum massa. In fermentum ante non turpis cursus fringilla. Praesent neque eros, gravida vel ante sed, vehicula elementum orci. Sed eu ipsum eget enim mattis mollis. Morbi eu ullamcorper urna, a condimentum massa. In fermentum ante non turpis cursus fringilla. Praesent neque eros, gravida vel ante sed, vehicula elementum orci. Sed eu ipsum eget enim mattis mollis. </p> </div> <Suspense fallback={<LoadingPosts />}> <Posts /> </Suspense> </main> ) }
ما Suspense API را از React وارد کرده و کامپوننت Posts
را درون آن قرار دادهایم. همینطور یک رابط کاربری fallback را نیز از کامپوننت LoadingPosts
اضافه کردهایم.
کامپوننت LoadingPosts
طرح لود پستها را نشان میدهد. این کامپوننت یک جلوه درخشان دارد و معمولاً به عنوان انیمیشن لود استفاده میشود و به گونهای طراحی شده است که به کاربران بازخورد بصری مبنی بر لود محتوا ارائه میدهد. اگر صفحه خود را مجدداً لود کنیم، طرح loading را برای یک دقیقه قبل از رندر شدن محتوای پستها مشاهده میکنیم.
همانطور که قبلاً در این مورد بحث کردیم، صفحاتی که لود دادههای داینامیک را دارند بهترین حالت استفاده برای partial pre-rendering هستند زیرا دادهها به صورت asynchronous دریافت میشوند. در ادامه یک مثال دیگر داریم که استفاده از ویژگی partial pre-rendering در آن میتواند بسیار مفید باشد.
در هر سایت وبلاگ، ما لیستی از وبلاگهای موجود را از سرور خود دریافت میکنیم. با استفاده از PPR، میتوانیم زمانی که پستهای وبلاگ را از سرور دریافت میکنیم، یک loader نمایش دهیم و پس از آماده شدن دادهها، آنها را جایگزین کنیم.
با قرار دادن fetching داده asynchronous داخل Suspense
، رندر کامپوننت را تا زمانی که دادهها در دسترس قرار بگیرند به حالت تعلیق در میآوریم. این رویکرد کارایی لود اولیه صفحه را با از پیش رندر کردن محتوای استاتیک، از جمله رابط کاربری fallback، بهینه میکند و تنها در صورت نیاز، دادههای داینامیک را دریافت و رندر مینماید. به عنوان مثال:
/* pages.js */ import Home from './components/Home'; const Page = () => ( <main className="flex min-h-screen flex-col justify-between p-12"> <header className="mb-12 text-center"> <h1 className="mb-6 font-bold text-3xl">MezieIV Blog</h1> </header> <Home /> <footer className="mt-24 text-center"> <p>©MezieIV 2023</p> </footer> </main> ); export default Page;
ما در این مقاله از App
router استفاده میکنیم. در کد بالا، یک header، body و footer در layout صفحه خود داریم. همینطور کامپوننت Home
که حاوی پستهای وبلاگ ما میباشد را هم import میکنیم.
در کامپوننت Home
، کد زیر را مینویسیم:
/* /components/Home.js */ import { Suspense } from 'react'; function LoadingPosts() { const shimmer = `relative overflow-hidden before:absolute before:inset-0 before:-translate-x-full before:animate-[shimmer_1.5s_infinite] before:bg-gradient-to-r before:from-transparent before:via-white/10 before:to-transparent`; return ( <div className="col-span-4 space-y-4 lg:col-span-1 min-h-screen w-full mt-20"> <div className={`relative h-[167px] rounded-xl bg-gray-900 ${shimmer}`} /> <div className="h-4 w-full rounded-lg bg-gray-900" /> <div className="h-6 w-1/3 rounded-lg bg-gray-900" /> <div className="h-4 w-full rounded-lg bg-gray-900" /> <div className="h-4 w-4/6 rounded-lg bg-gray-900" /> </div> ); } async function Posts() { const data = await fetch(`https://jsonplaceholder.typicode.com/posts`, { cache: 'no-store', }); const posts = await data.json(); return ( <> <h2 className="mb-3 mt-8 font-bold text-2xl">All Posts</h2> {posts.slice(0, 7).map((post) => ( <div key={post.id} className="mb-5" > <h4 className="text-lg">Title: {post.title}</h4> <p className="text-sm">Content: {post.body}</p> </div> ))} </> ); } export default function Home() { return ( <> <div> <h2 className="mb-3 font-bold text-2xl"> Partial Pre-Rendering </h2> <p> Morbi eu ullamcorper urna, a condimentum massa. In fermentum ante non turpis cursus fringilla. Praesent neque eros, gravida vel ante sed, vehicula elementum orci. Sed eu ipsum eget enim mattis mollis. Morbi eu ullamcorper urna, a condimentum massa. In fermentum ante non turpis cursus fringilla. Praesent neque eros, gravida vel ante sed, vehicula elementum orci. Sed eu ipsum eget enim mattis mollis. </p> </div> <Suspense fallback={<LoadingPosts />}> <Posts /> </Suspense> </> ); }
header و پاراگرافهای صفحه و همچنین رابط کاربری fallback از قبل رندر شدهاند. هنگامی که صفحه خود را لود کنیم، میتوانیم تا زمانی که دریافت اطلاعات کامل شده و پستهای وبلاگ آماده نمایش شوند، loader را ببینیم.
در مثال بالا، ما فقط یک بخش داریم که از Suspense API مربوط به PPR استفاده میکند.
پس از این که کاربر بر روی یکی از پستهای وبلاگ کلیک میکند، به صفحه آن وبلاگ هدایت میشود تا به خواندن پست وبلاگ ادامه دهد. ما میخواهیم که کاربر در صفحه وبلاگ، پست وبلاگ و همچنین وبلاگهای مشابه یا پستهای اخیر وبلاگ را مشاهده کند. این اتفاق یک تجربه کاربری خوب در یک سناریوی واقعی است. در نتیجه کاربر برای این که پست دیگری را مشاهده کند نیازی ندارد که به صفحه قبل برگردد.
از آنجایی که هم وبلاگ اصلی و هم پستهای دیگر به صورت داینامیک دریافت میشوند، میتوانیم از Suspense API برای هر دو بخش استفاده کنیم.
میتوانیم کد زیر را در داخل کامپوننت Home
، کپی و جایگزین نماییم:
/* /components/Home.js */ import { Suspense } from 'react'; function LoadingPosts() { const shimmer = `relative overflow-hidden before:absolute before:inset-0 before:-translate-x-full before:animate-[shimmer_1.5s_infinite] before:bg-gradient-to-r before:from-transparent before:via-white/10 before:to-transparent`; return ( <div className="col-span-4 space-y-4 lg:col-span-1 w-full mt-20"> <div className={`relative h-[167px] rounded-xl bg-gray-900 ${shimmer}`} /> <div className="h-4 w-full rounded-lg bg-gray-900" /> <div className="h-6 w-1/3 rounded-lg bg-gray-900" /> <div className="h-4 w-full rounded-lg bg-gray-900" /> <div className="h-4 w-4/6 rounded-lg bg-gray-900" /> </div> ); } async function fetchPosts() { return new Promise((resolve) => { setTimeout(async () => { const data = await fetch( `https://jsonplaceholder.typicode.com/posts`, { cache: 'no-store', } ); const posts = await data.json(); resolve(posts); }, ۲۰۰۰); }); } async function BlogPost() { const posts = await fetchPosts(); const post = posts[0]; return ( <div className="w-full"> <h4 className="text-lg mb-2">Title - {post.title}</h4> <p className="text-sm leading-6"> {post.body} {post.body} {post.body} {post.body} {post.body}{' '} {post.body} {post.body} {post.body} {post.body} {post.body} </p> </div> ); } async function Aside() { const posts = await fetchPosts(); return ( <aside className="w-full"> <div> {posts.slice(0, 5).map((post) => ( <ol key={post.id} style={{ listStyle: 'inside' }} > <li className="text-lg w-full"> <a href="#">{post.title}</a> </li> </ol> ))} </div> </aside> ); } export default function Home() { return ( <div className="flex justify-between pl-12 pr-12"> <div className="w-[70%]"> <h2 className="text-2xl mb-6">Main Blog</h2> <Suspense fallback={<LoadingPosts />}> <BlogPost /> </Suspense> </div> <div className="w-[25%] pl-10"> <h2 className="text-2xl mb-12">Latest Blog Posts</h2> <Suspense fallback={<LoadingPosts />}> <Aside /> </Suspense> </div> </div> ); }
همانطور که در کد بالا میبینیم، کامپوننتهای BlogPost
و Aside
را درون دو Suspense API مجزا قرار دادیم. ما از یک طرح loader یکسان برای هر دو کامپوننت استفاده میکنیم اما این امکان وجود دارد که بسته به طراحی رابط کاربری که داریم طرحهای منحصربهفردی را ایجاد نماییم. این کار با رابط کاربری که داریم بسیار سازگارتر بوده و از نظر زیبایی شناسی دلپذیرتر به نظر میرسد.
یک نمونه دیگر استفاده از PPR، داشبورد ادمین است. داشبوردها دارای بخشهای مختلفی هستند که شامل نمودارهایی مانند نمودارهای میلهای و دایرهای، فهرست اطلاعات و غیره میباشد. در چنین مواقعی میتوانیم در بخشهایی که دادهها به صورت asynchronous دریافت میشوند، از قابلیت partial pre-rendering استفاده کنیم. به طور مشابه، بخشهایی که به صورت asynchronous دریافت نمیشوند در زمان ساخت به صورت استاتیک از قبل رندر خواهند شد.
با توجه به مفاهیمی که در این مقاله به آنها پرداختیم، میتوانیم نتیجه بگیریم که partial pre-rendering مزایای زیر را دارد:
در این مقاله به بررسی partial pre-rendering و نحوه عملکرد آن در برنامه Next.js پرداختیم.
استفاده از partial pre-rendering در سناریوهایی که بخشهایی از وبسایت ما به صورت داینامیک رندر میشوند یا دادهها بهصورت asynchronous دریافت میشوند، سودمند است. با استفاده از PPR، ما انتخاب میکنیم که کدام بخش از صفحهای که داریم باید از قبل رندر شود و کدام قسمت باید در صورت تقاضا لود گردد. این کار باعث میشود تا صفحه اولیه سریعتر لود شود. به این ترتیب، کاربران بلافاصله محتوای استایتک یا از پیش رندر شده را مشاهده میکنند و محتوای داینامیک هنگامی که نیاز باشد یا پس از این که آماده شود، به نمایش در میآید.
اگرچه این مفهوم هنوز در مرحله اولیه و آزمایشی خود است و استفاده از آن در پروژههای واقعی توصیه نمیشود. اما میتوانیم آن را امتحان کنیم تا نگاهی به ظاهر یک نسخه پایدار داشته باشیم.
دیدگاهها: