Caching در Frontend؛ از Browser Cache تا Data Cache در Next.js

Caching در Frontend؛ از Browser Cache تا Data Cache در Next.js

توسعه‌دهنده فرانت‌اند

چرا Caching برای Frontend اهمیت دارد؟

Caching در Frontend؛ از Browser Cache تا Data Cache در Next.js

صارم توکلی

۸ دقیقه مطالعه

۱ نفر

۱۴۰۵/۶/۲۹

Caching در Frontend؛ از Browser Cache تا Data Cache در Next.js

وقتی یک کاربر برای اولین بار وارد یک سایت می‌شود، مرورگر باید منابع مختلفی را دریافت کند؛ از فایل‌های JavaScript و CSS گرفته تا تصاویر، فونت‌ها و داده‌های مورد نیاز صفحه.

اگر تمام این اطلاعات در هر بار درخواست دوباره از ابتدا دریافت شوند، هم زمان پاسخ افزایش پیدا می‌کند و هم مصرف منابع Server و شبکه بیشتر می‌شود.

اینجاست که Caching وارد معماری Frontend می‌شود.

Cache در ساده‌ترین تعریف، محلی برای نگهداری موقت داده‌هایی است که احتمال دارد دوباره مورد استفاده قرار بگیرند. اما در یک پروژه مدرن، فقط یک Cache وجود ندارد. داده می‌تواند در Browser، CDN، Application، Data Layer یا حتی در لایه‌های مختلف معماری Next.js ذخیره شود.

بنابراین برای طراحی یک Frontend سریع، مهم است بدانیم:

چه چیزی را Cache کنیم، کجا Cache کنیم و چه زمانی Cache را دوباره اعتبارسنجی کنیم؟


چرا Caching برای Frontend اهمیت دارد؟

فرض کنید یک کاربر وارد صفحه فروشگاه می‌شود.

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

  • فایل‌های JavaScript

  • فایل‌های CSS

  • فونت‌ها

  • تصاویر

  • اطلاعات محصولات

  • دسته‌بندی‌ها

  • اطلاعات کاربر

  • تنظیمات صفحه

اگر هر بار همه این اطلاعات از Server دریافت شوند، درخواست‌های غیرضروری زیادی ایجاد می‌شود.

Caching می‌تواند باعث کاهش تعداد درخواست‌ها، کاهش حجم انتقال داده و بهبود زمان پاسخ شود.

اما مهم‌تر از افزایش سرعت، کاهش فشار روی زیرساخت نیز هست.

اگر یک API در هر درخواست اطلاعات یکسانی را از Database دریافت کند، در مقیاس بالا هزینه پردازشی قابل‌توجهی ایجاد خواهد شد.

Cache می‌تواند بخشی از این بار را از Server حذف کند.


Browser Cache چیست؟

اولین لایه‌ای که معمولاً با آن مواجه می‌شویم Browser Cache است.

مرورگر می‌تواند برخی منابع سایت را برای استفاده‌های بعدی ذخیره کند.

برای مثال:

  • JavaScript

  • CSS

  • Image

  • Font

  • بعضی Responseهای HTTP

در این حالت، اگر کاربر دوباره به همان سایت مراجعه کند، مرورگر ممکن است به‌جای دریافت مجدد Resource از Server، نسخه Cache شده را استفاده کند.

این موضوع مخصوصاً برای فایل‌های Static اهمیت زیادی دارد.


Cache-Control؛ یکی از مهم‌ترین بخش‌های HTTP Caching

رفتار Browser Cache تا حد زیادی توسط Headerهای HTTP کنترل می‌شود.

یکی از مهم‌ترین Headerها:

Cache-Control

است.

برای مثال:

Cache-Control: max-age=3600

به مرورگر اعلام می‌کند که Resource می‌تواند برای مدت مشخصی معتبر در نظر گرفته شود.

اما در پروژه‌های واقعی معمولاً مسئله کمی پیچیده‌تر است.

برای فایل‌هایی مانند JavaScript و CSS که نام فایل آن‌ها همراه با Hash تغییر می‌کند، می‌توان مدت Cache طولانی‌تری در نظر گرفت.

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


Cache فقط برای فایل‌های Static نیست

یکی از تصورات اشتباه این است که Caching را فقط برای تصاویر، CSS و JavaScript در نظر بگیریم.

در یک معماری مدرن Frontend، داده‌های API نیز می‌توانند Cache شوند.

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

GET /api/products

اگر اطلاعات محصولات دائماً تغییر نکنند، دریافت دوباره آن‌ها برای هر Request ممکن است غیرضروری باشد.

در چنین شرایطی می‌توان Response را Cache کرد و برای مدت مشخصی از نسخه ذخیره‌شده استفاده کرد.

این موضوع به‌خصوص در سایت‌هایی با Traffic بالا اهمیت زیادی دارد.


Client-Side Data Cache

در اپلیکیشن‌های React، گاهی داده‌های دریافت‌شده از API در سمت Client نیز Cache می‌شوند.

کتابخانه‌هایی مانند TanStack Query امکاناتی برای مدیریت این نوع داده‌ها فراهم می‌کنند.

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

در اینجا Cache فقط برای افزایش سرعت نیست.

مدیریت صحیح Cache می‌تواند مواردی مانند:

  • جلوگیری از درخواست‌های تکراری

  • Background Refetch

  • مدیریت Loading

  • مدیریت Stale Data

  • Synchronization

  • Invalidating داده‌ها

را نیز ساده‌تر کند.


تفاوت Cache با State چیست؟

این دو مفهوم معمولاً با یکدیگر اشتباه گرفته می‌شوند.

State معمولاً بیانگر وضعیت فعلی Application است.

اما Cache بیشتر برای نگهداری داده‌ای استفاده می‌شود که احتمال دارد دوباره مورد نیاز قرار گیرد.

برای مثال:

اطلاعات باز یا بسته بودن Sidebar می‌تواند State باشد.

اما لیست محصولاتی که از API دریافت کرده‌اید، می‌تواند Server Data باشد که Cache شده است.

این تفاوت در پروژه‌های بزرگ اهمیت زیادی دارد.

اگر Server Data را صرفاً به‌عنوان Global State مدیریت کنیم، ممکن است معماری پیچیده‌تری نسبت به نیاز واقعی پروژه ایجاد شود.


Caching در Next.js

در Next.js موضوع Caching اهمیت بیشتری پیدا می‌کند؛ چون Framework می‌تواند در چندین لایه با Cache کار کند.

در App Router، نحوه دریافت داده، Rendering و Cache شدن اطلاعات می‌تواند روی رفتار نهایی صفحه تأثیر بگذارد.

به همین دلیل در پروژه‌های Next.js باید بین موارد مختلف تفاوت قائل شویم:

  • Request Data

  • Full Route

  • Client Router

  • Browser Cache

  • CDN یا Edge Cache

  • Cache مربوط به Data Fetching

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


Data Cache در Next.js

یکی از مفاهیم مهم در معماری Next.js، Data Cache است.

هدف این لایه، نگهداری نتیجه برخی Data Requestها برای استفاده مجدد است.

برای مثال تصور کنید در یک Server Component اطلاعات محصولات را دریافت می‌کنید.

اگر درخواست به شکلی انجام شود که قابل Cache شدن باشد، نتیجه می‌تواند برای درخواست‌های بعدی مورد استفاده قرار گیرد.

این موضوع باعث می‌شود در شرایط مناسب، درخواست‌های تکراری به Data Source کاهش پیدا کنند.


چرا Cache کردن همه چیز اشتباه است؟

Caching همیشه به معنی بهتر شدن سیستم نیست.

فرض کنید یک سایت فروشگاهی دارید و موجودی محصول هر چند دقیقه تغییر می‌کند.

اگر اطلاعات موجودی را برای مدت بسیار طولانی Cache کنید، کاربر ممکن است اطلاعات قدیمی مشاهده کند.

در سیستم‌های حساس، داده قدیمی می‌تواند حتی باعث ایجاد خطای منطقی شود.

بنابراین سؤال اصلی این نیست که:

«چطور همه چیز را Cache کنیم؟»

بلکه باید بپرسیم:

«این داده چه مدت می‌تواند معتبر باقی بماند؟»


Static Data و Dynamic Data

یکی از روش‌های ساده برای طراحی Cache Policy، تقسیم داده‌ها به Static و Dynamic است.

داده‌های نسبتاً Static

برای مثال:

  • لیست دسته‌بندی‌ها

  • تنظیمات عمومی سایت

  • بعضی مقالات

  • اطلاعات Footer

  • برخی محتواهای Marketing

این داده‌ها معمولاً قابلیت Cache شدن بیشتری دارند.

داده‌های Dynamic

برای مثال:

  • موجودی محصول

  • سبد خرید

  • وضعیت پرداخت

  • اطلاعات لحظه‌ای حساب کاربر

  • Notificationهای جدید

این داده‌ها معمولاً نیازمند استراتژی متفاوتی هستند.

در اینجا TTL کوتاه‌تر، Revalidation یا حتی عدم Cache می‌تواند منطقی‌تر باشد.


Revalidation چیست؟

یکی از راهکارهای مهم برای جلوگیری از قدیمی شدن Cache، Revalidation است.

در این روش به‌جای اینکه Cache برای همیشه معتبر باشد، پس از یک بازه مشخص دوباره بررسی می‌شود.

فرض کنید اطلاعات یک بخش از سایت هر ۱۰ دقیقه تغییر می‌کند.

می‌توان سیاستی طراحی کرد که داده حداکثر برای یک بازه مشخص Cache شود و سپس مجدداً اعتبار آن بررسی شود.

این روش یک تعادل میان Performance و Freshness ایجاد می‌کند.


Cache Invalidation؛ سخت‌ترین بخش Caching

یکی از معروف‌ترین مشکلات سیستم‌های Cache این است:

چه زمانی باید Cache را پاک یا Invalid کنیم؟

فرض کنید محصولی را از پنل مدیریت ویرایش کرده‌اید.

Database اطلاعات جدید را دارد، اما نسخه قبلی هنوز در Cache قرار دارد.

اگر Cache Invalid نشود، ممکن است کاربر همچنان اطلاعات قبلی را مشاهده کند.

بنابراین هر سیستم Cache جدی به یک استراتژی برای Invalidation نیاز دارد.

برای مثال:

  • Revalidation بر اساس زمان

  • Invalid کردن بر اساس Tag

  • Invalid کردن هنگام Mutation

  • حذف Cache پس از تغییر داده

انتخاب روش مناسب به نوع داده و معماری سیستم بستگی دارد.


Cache و Mutation

در عملیات‌هایی مانند ایجاد، ویرایش و حذف داده، مسئله Cache اهمیت بیشتری پیدا می‌کند.

فرض کنید کاربر یک محصول را ویرایش می‌کند.

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

بنابراین سیستم باید بتواند Cache مربوط به آن داده را دوباره Validate یا Invalidate کند.

این موضوع یکی از دلایلی است که مدیریت Cache در پروژه‌های بزرگ به یک بخش جدی از معماری تبدیل می‌شود.


یک اشتباه رایج در پروژه‌های Next.js

یکی از اشتباهات رایج این است که توسعه‌دهنده فقط به Server Component یا Client Component بودن یک بخش توجه کند و رفتار Cache را نادیده بگیرد.

اما در پروژه‌های بزرگ باید چند سؤال را هم‌زمان بررسی کرد:

این داده از کجا می‌آید؟

چه کسی مالک آن است؟

چقدر تغییر می‌کند؟

آیا باید Cache شود؟

چه مدت معتبر است؟

چه زمانی باید Revalidate شود؟

بعد از Mutation چه اتفاقی برای Cache می‌افتد؟

آیا داده به کاربر خاصی وابسته است؟

پاسخ به این سؤال‌ها، Cache Strategy مناسب را مشخص می‌کند.


یک معماری ساده برای فکر کردن درباره Cache

می‌توان جریان داده را به شکل زیر تصور کرد:

Database → Server → Data Cache → Application → Browser → UI

البته در معماری‌های واقعی ممکن است CDN، API Gateway، Reverse Proxy و لایه‌های دیگری نیز بین این بخش‌ها قرار داشته باشند.

نکته مهم این است که بدانیم هر Cache چه داده‌ای را نگهداری می‌کند و مسئولیت آن چیست.

وقتی این مرزها مشخص باشند، Debug کردن مشکلات Cache نیز ساده‌تر می‌شود.


Caching؛ ابزار Performance یا بخشی از معماری؟

Caching در ابتدا ممکن است فقط یک تکنیک Performance به نظر برسد.

اما در پروژه‌های بزرگ، تصمیمات مربوط به Cache مستقیماً با معماری سیستم ارتباط دارند.

اگر Cache بیش از حد aggressive باشد، ممکن است داده‌های قدیمی نمایش داده شوند.

اگر Cache بیش از حد محدود باشد، Performance کاهش پیدا می‌کند و فشار بیشتری به Server وارد می‌شود.

بنابراین یک Cache Strategy مناسب باید بین چند عامل تعادل ایجاد کند:

Performance

Freshness

Consistency

Server Load

User Experience


جمع‌بندی

Caching یکی از مهم‌ترین ابزارها برای ساخت Frontendهای سریع و مقیاس‌پذیر است، اما استفاده صحیح از آن فقط به اضافه کردن یک Cache Layer محدود نمی‌شود.

در یک پروژه مدرن، باید تفاوت بین Browser Cache، Client Data Cache، Server/Data Cache و سایر لایه‌های Cache را بدانیم.

در Next.js نیز شناخت رفتار Cache و Revalidation اهمیت ویژه‌ای دارد؛ زیرا نحوه دریافت داده و نوع Rendering می‌تواند روی استراتژی Cache تأثیر بگذارد.

در نهایت، هدف این نیست که بیشترین مقدار ممکن را Cache کنیم.

هدف این است که داده مناسب، در لایه مناسب، برای مدت مناسب Cache شود.

همین نگاه می‌تواند تفاوت بین یک پروژه Frontend معمولی و یک معماری واقعاً مقیاس‌پذیر را ایجاد کند.