پرش به محتوای اصلی

کالبدشکافی پیاده‌سازی ClientAppShell و Partial Prerendering برای حذف پرش فریم در داشبوردهای سازمانی

معماری اپ‌شل پایدار در وب‌اپلیکیشن‌های B2B با Next.js 16

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

۳۰ مهر ۱۴۰۵• ۶ دقیقه مطالعه• نوشتهٔ سجاد سمائی
Sajjad Samaei, product designer and full-stack engineer

ساختار پوسته پایدار و استریم موازی با مرزهای Suspense در Next.js 16

ساختار پوسته پایدار و استریم موازی با مرزهای Suspense در Next.js 16
سرفصل‌های مقاله

کاربر سازمانی که روزی هشت ساعت با یک پنل مالی یا داشبورد عملیاتی کار می‌کند، حوصله دیدن اسکلتون‌های تکراری در سایدبار یا پرش فریم (Layout Shift) هنگام جابه‌جایی بین تب‌ها را ندارد. در بسیاری از پورتال‌های سازمانی که با رویکردهای سنتی رندر سمت سرور ساخته شده‌اند، هر تغییر مسیر (Route Transition) باعث اجرای مجدد کوئری‌های احراز هویت، بررسی مجدد دسترسی‌ها و بازسازی کامل درخت کامپوننت‌ها می‌شود. نتیجه، محصولی است که روی کاغذ مدرن است اما در عمل حس یک وب‌سایت کند و ناپایدار را به کاربر القا می‌کند. پیاده‌سازی درست معماری اپ شل nextjs 16 مرز بین یک صفحه وب معمولی و یک نرم‌افزار تحت وب با حس و حال نیتیو (Desktop-grade) را مشخص می‌کند.

چرا پنل‌های کاری در تغییر مسیرها دچار پرش و مکث می‌شوند؟

ریشه اصلی کندی و پرش تصویر در داشبوردهای سازمانی، گره خوردن منطق احراز هویت و واکشی داده‌های سراسری به چرخه رندر هر صفحه در سمت سرور است. وقتی در معماری App Router بدون جداسازی لایه پوسته (Shell) از لایه داده، کاربر را از مسیر /invoices به /ledgers هدایت می‌کنید، در صورت استفاده نادرست از dynamic = 'force-dynamic' در لایوت‌های میانی یا فراخوانی مستقیم وضعیت سشن در کامپوننت‌های والد، مرورگر منتظر پاسخ کامل سرور می‌ماند.

در این حالت، حتی اگر Next.js سعی کند Layout را حفظ کند، مسدود شدن رشته اصلی رندر به خاطر اعتبارسنجی توکن‌ها یا چک کردن نقش کاربر (RBAC) در پایگاه‌داده، ناوبری فوری (Instant Navigation) را از بین می‌برد. کاربر روی منو کلیک می‌کند، تا چندصد میلی‌ثانیه هیچ اتفاقی نمی‌افتد و ناگهان کل صفحه با یک اسکلتون سراسری جایگزین می‌شود. در نرم‌افزارهای B2B که سرعت ورود داده و جابه‌جایی بین جداول اولویت اول است، این مکث‌ها مستقیماً بهره‌وری تیم‌های عملیاتی را کاهش می‌دهد.

تفاوت Partial Prerendering (PPR) با SSR سنتی در مدیریت پوسته

معماری PPR (پیش‌رندر جزئی) در Next.js 16 به جای اینکه کل صفحه را در لحظه درخواست بسازد، پوسته ایستا را در کسری از ثانیه از لبه شبکه (Edge/CDN) تحویل می‌دهد و فقط بخش‌های وابسته به داده را استریم می‌کند. در SSR سنتی، زمان رسیدن به اولین بایت (TTFB) برابر با کندترین کوئری صفحه است؛ یعنی تا زمانی که دیتابیس وضعیت سشن و داده‌های جدول را برنگرداند، کاربر صفحه سفید می‌بیند.

با فعال‌سازی PPR و ترکیب آن با دستورالعمل جدید 'use cache' در Next.js 16، ساختار اصلی برنامه شامل نوار کناری، هدر، ساختار گرید و استایل‌ها در زمان بیلد یا در کش سطح سرور منجمد می‌شوند. حفره‌های پویا (Dynamic Holes) که با کامپوننت <Suspense> مشخص شده‌اند، در همان اتصال واحد HTTP به مرورگر استریم می‌شوند:

// app/(dashboard)/layout.tsx import { Suspense } from 'react' import { Sidebar } from '@/components/shell/sidebar' import { TopNav } from '@/components/shell/top-nav' import { UserSessionGuard } from '@/components/auth/session-guard' import { ShellSkeleton } from '@/components/shell/skeleton' export default function DashboardLayout({ children }: { children: React.ReactNode }) { return ( <div className="grid min-h-screen grid-cols-[260px_1fr] bg-neutral-950 text-neutral-100"> {/* پوسته کاملاً ایستا؛ بدون وابستگی به کوئری‌های مسدودکننده */} <Sidebar /> <div className="flex flex-col overflow-hidden"> <TopNav /> <main className="flex-1 overflow-y-auto p-6"> {/* جداسازی مرز داده‌های پویا و سشن از اسکلت اصلی */} <Suspense fallback={<ShellSkeleton />}> <UserSessionGuard>{children}</UserSessionGuard> </Suspense> </main> </div> </div> ) }

در این ساختار، کلیک روی هر لینک در سایدبار بلافاصله URL را تغییر داده و فریم اصلی را بدون کوچک‌ترین لرزشی ثابت نگه می‌دارد، در حالی که محتوای داخل <main> در پس‌زمینه بارگذاری می‌شود.

پیاده‌سازی ClientAppShell برای ایجاد حس نرم‌افزار نیتیو دسکتاپ

برای دستیابی به تجربه کاربری مشابه نرم‌افزارهای دسکتاپ مانند Linear یا Figma، باید وضعیت‌های رابط کاربری (UI State) و کش کلاینت را در یک لایه پایدار به نام ClientAppShell نگه دارید تا با تغییر مسیرها از بین نروند. نرم‌افزارهای نیتیو هنگام تغییر تب، وضعیت اسکرول، فیلترهای اعمال‌شده، پنجره‌های مودال باز یا پالت فرمان (Command Palette) را ریست نمی‌کنند.

در این الگو، یک کامپوننت کلاینت در بالاترین سطح زیرینِ لایوت اصلی قرار می‌گیرد که وظیفه مدیریت وضعیت‌های سراسری، میانبرهای صفحه‌کلید (مانند Cmd+K) و همگام‌سازی خوش‌بینانه (Optimistic Updates) را بر عهده دارد:

// components/shell/client-app-shell.tsx 'use client' import { useState, useEffect } from 'react' import { usePathname } from 'next/navigation' import { CommandPalette } from '@/components/ui/command-palette' import { WorkspaceProvider } from '@/context/workspace-context' export function ClientAppShell({ initialWorkspace, children, }: { initialWorkspace: WorkspaceData children: React.ReactNode }) { const [isCmdOpen, setIsCmdOpen] = useState(false) const pathname = usePathname() useEffect(() => { const down = (e: KeyboardEvent) => { if (e.key === 'k' && (e.metaKey || e.ctrlKey)) { e.preventDefault() setIsCmdOpen((open) => !open) } } document.addEventListener('keydown', down) return () => document.removeEventListener('keydown', down) }, []) return ( <WorkspaceProvider initialData={initialWorkspace}> <div className="relative flex h-full w-full flex-col" data-route={pathname}> {children} <CommandPalette open={isCmdOpen} onOpenChange={setIsCmdOpen} /> </div> </WorkspaceProvider> ) }

نگه‌داشتن این لایه در حافظه مرورگر باعث می‌شود داده‌های پایه مثل اطلاعات ورک‌اسپیس، لیست پروژه‌های کاربر و تنظیمات پوسته (تم تاریک/روشن) تنها یک‌بار در ابتدای ورود مقداردهی شوند. در جابه‌جایی‌های بعدی، React فقط زیررده‌های تغییریافته را آشتی (Reconcile) می‌دهد و هیچ بازخوانی اضافه‌ای به سمت سرور برای دریافت مجدد اطلاعات پایه ارسال نمی‌شود.

استریم موازی داده‌ها با Suspense و جلوگیری از آبشار درخواست‌ها

شکستن صفحات پیچیده به مرزهای مستقل Suspense تنها راه جلوگیری از قفل شدن رابط کاربری در برابر کوئری‌های سنگین تحلیلی است. در یک داشبورد B2B، معمولاً چندین منبع داده با سرعت‌های متفاوت وجود دارد؛ برای مثال، لیست آخرین تراکنش‌ها در ۳۰ میلی‌ثانیه از PostgreSQL خوانده می‌شود، اما محاسبه نمودار جریان نقدینگی ممکن است ۸۰۰ میلی‌ثانیه زمان ببرد.

اگر هر دو کوئری را در سطح page.tsx با await متوالی یا حتی Promise.all مسدود کنید، کاربر تا پایان اجرای سنگین‌ترین کوئری هیچ داده‌ای نمی‌بیند. الگوی صحیح در Next.js 16، انتقال واکشی داده به پایین‌ترین برگ‌های درخت کامپوننت (Server Components) یا پاس دادن مستقیم Promise به کامپوننت‌های کلاینت و باز کردن آن با هوک use است.

در استودیو سجاد، ما معماری فرانت‌اند و زیرساخت پنل‌های سازمانی را طوری مهندسی می‌کنیم که زیر بار داده‌های سنگین، سرعتی در سطح نرم‌افزارهای بومی دسکتاپ ارائه دهند. اگر پورتال B2B یا داشبورد عملیاتی شما با کندی ناوبری، پیچیدگی مدیریت وضعیت یا بدهی فنی در لایه رندرینگ روبه‌روست، برای بررسی معماری و بازطراحی زیرساخت خود به صفحه خدمات استودیو سجاد سر بزنید.

اشتراک‌گذاری مقاله

یادداشت‌های مرتبط

ادامهٔ مطالعه پیرامون تفکر محصول، دیزاین و مهندسی نرم‌افزار.

پروژه‌ای در ذهن دارید؟

بنویسید؛ ظرف کمتر از ۲۴ ساعت پاسخ می‌دهیم و قدم بعدی را با هم مشخص می‌کنیم.