نحوه ایجاد یک برنامه وب پیشرو (PWA) با استفاده از Next.js
آیا تا به حال خواسته اید یک برنامه وب ایجاد کنید که به راحتی بر روی هر دستگاهی کار کند - خواه روی وب، تلفن همراه یا دسکتاپ باشد؟ تصور کنید برنامه شما می تواند به سرعت بارگیری شود، بدون اتصال به اینترنت کار کند، و مانند یک برنامه بومی احساس کنید، همه بدون نیاز به نصب از فروشگاه برنامه. این دقیقاً همان کاری است که برنامه های وب پیشرو (PWA) می توانند انجام دهند.
در این آموزش، نحوه ساخت PWA با استفاده از Next.js را یاد خواهید گرفت. ما با ایجاد یک وب سایت جستجوی فیلم کاربردی با این ابزار شروع می کنیم. پس از راهاندازی اصول اولیه، این برنامه را به یک PWA تبدیل میکنیم و پشتیبانی آفلاین و زمان بارگذاری سریعتر را اضافه میکنیم. در پایان، شما یک PWA قدرتمند خواهید داشت که تجربه کاربری روانی را در تمام پلتفرمها ارائه میدهد—همه از یک پایگاه کد واحد.
آنچه را پوشش خواهیم داد
راهاندازی پروژه: ما با ایجاد برنامه جستجوی فیلم با استفاده از Next.js شروع میکنیم، که در سال 2024 یک انتخاب ایدهآل برای ساخت برنامههای React سریع و قابل اعتماد است که به خوبی در همه دستگاهها کار میکنند.
تبدیل برنامه به PWA: در مرحله بعد، مراحل تبدیل برنامه به یک برنامه وب پیشرفته را طی می کنیم که ویژگی های کلیدی و بهترین شیوه های PWA را پوشش می دهد.
گفت ن پشتیبانی آفلاین: در نهایت، ما با اجرای قابلیتهای آفلاین، مطمئن میشویم که برنامه شما حتی زمانی که اتصال اینترنتی وجود ندارد، فعال میماند.
در اینجا برنامه نهایی چگونه خواهد بود:
را نشان می دهد و طراحی براق و قابلیت های آفلاین آن را برجسته می کند." class="image--center mx-auto" width="2624" height="1754" loading="lazy">
مخاطب
این آموزش برای توسعه دهندگان React در همه سطوح است، خواه تازه شروع کرده باشید یا قبلاً تجربه داشته باشید. اگر میخواهید برنامههای وب خود را با آپشن های PWA تقویت کنید، این راهنما شما را از مراحل لازم راهنمایی میکند.
پیش نیازها
قبل از شروع، مطمئن شوید که با React.js و Next.js آشنایی دارید. اگر با PWA ها تازه کار هستید، ممکن است بخواهید چند مقاله مقدماتی را بخوانید تا مروری سریع داشته باشید.
برنامه های وب پیشرو چیست؟ راهنمای PWA برای مبتدیان
برنامه های وب پیشرفته را یاد بگیرید
برنامه وب پیشرو (PWA) چیست؟
برنامه وب پیشرو (PWA) نوعی برنامه وب است که با استفاده از فناوری های وب استاندارد مانند HTML، CSS و جاوا اسکریپت ساخته شده است. PWA ها روی وب، دسکتاپ و دستگاه های تلفن همراه کار می کنند و بهترین ویژگی های وب و برنامه های بومی را برای ارائه تجربه ای سریع، قابل اعتماد و جذاب ترکیب می کنند.
چیزی که PWA ها را خاص می کند، توانایی آنها در کار آفلاین، ارسال اعلان های فشار و نصب بر روی دستگاه کاربر بدون فروشگاه برنامه است. به طور خلاصه، یک PWA باعث می شود برنامه وب شما شبیه یک برنامه بومی باشد و در عین حال انعطاف پذیری و دسترسی گسترده به وب را حفظ می کند.
چرا برنامه وب خود را به PWA تبدیل کنید؟
تبدیل برنامه وب شما به PWA چندین مزیت را به همراه دارد:
در دسترس بودن چند پلتفرم: یک PWA روی هر دستگاهی با مرورگر کار میکند، پس شما فقط باید یک پایگاه کد را برای برنامههای وب، موبایل و دسکتاپ توسعه دهید و نگهداری کنید. این باعث صرفه جویی در زمان و تضمین یک تجربه ثابت در همه سیستم عامل ها می شود.
را نشان می دهد که بر روی تلفن همراه، مرورگر وب و دسکتاپ اجرا می شود و ماهیت همه کاره PWA ها را نشان می دهد." width="1600" height="661" loading="lazy">
قابلیت های آفلاین: PWA ها می توانند به صورت آفلاین یا در مناطقی با اتصال ضعیف با ذخیره منابع ضروری کار کنند و برنامه شما را حتی بدون دسترسی به اینترنت نیز فعال نگه دارند.
عملکرد بهبود یافته: PWA ها به لطف تکنیک هایی مانند سرویس دهنده ها و حافظه پنهان ساخته شده اند تا سریع بارگیری شوند و حتی در شبکه های کند کار کنند.
افزایش تعامل کاربر: کاربران می توانند بدون نیاز به فروشگاه برنامه، PWA را مستقیماً به صفحه اصلی خود اضافه کنند. این دسترسی آسان، همراه با آپشن های ی مانند اعلانهای فشاری، کمک میکند تا کاربران را درگیر کرده و برگردند.
بیشتر بخوانید
معایب PWA ها
در حالی که PWA ها مزایای زیادی دارند، چند جنبه منفی نیز وجود دارد:
دسترسی محدود به آپشن های دستگاه : PWAها به آپشن های خاص دستگاه مانند بلوتوث یا کنترلهای پیشرفته دوربین دسترسی کامل ندارند. برای برنامه هایی که نیاز به یکپارچه سازی سخت افزاری عمیق دارند، این می تواند یک محدودیت باشد.
دید کمتر : از آنجایی که PWA ها از فروشگاه های برنامه عبور نمی کنند، دیدی را که فروشگاه های برنامه ارائه می دهند از دست می دهند. برخی از کاربران نیز ممکن است ترجیح دهند برنامه ها را از فروشگاه های برنامه دانلود کنند تا مستقیماً از مرورگر.
پشتیبانی محدود از iOS : برخی از آپشن های PWA، مانند اعلانهای فشار، در آیفون و آیپد در مقایسه با دستگاههای Android به خوبی کار نمیکنند، که میتواند تعامل با کاربران iOS را محدود کند.
شروع به کار: راه اندازی پروژه Next.js
اکنون که در مورد مزایای PWA صحبت کردیم، اجازه دهید وارد پیاده سازی واقعی شویم. ما با تنظیم فایل های لازم در پروژه خود شروع می کنیم.
چرا Next.js را در سال 2024 انتخاب کنید؟
Next.js یک انتخاب برتر برای ساخت برنامههای React در سال 2024 است. آپشن های ی مانند رندر سمت سرور و تولید سایت استاتیک را ارائه میکند که ایجاد برنامههای وب سریع و قابل اعتماد را آسانتر میکند. این ویژگیها تضمین میکنند که برنامه شما در همه دستگاهها عملکرد خوبی دارد و حتی به صورت آفلاین کار میکند.
نصب پروژه
برای راه اندازی پروژه Next.js خود این مراحل را دنبال کنید:
کلون کردن مخزن: ترمینال خود را باز کرده و اجرا کنید:
git clone https://github.com/iamspruce/MovieMaster.git
به فهرست پروژه خود بروید:
cd your-repo
Install Dependencies: بسته های مورد نیاز را با:
npm install
پیکربندی متغیرهای محیط: یک فایل .env.local در فهرست اصلی ایجاد کنید و کلید OMDB API خود را اضافه کنید:
NEXT_PUBLIC_OMDB_API_KEY=your-api-key
می توانید کلید API خود را از وب سایت OMDB API دریافت کنید.
چرا کلید OMDB API مورد نیاز است؟
کلید OMDB API به PWA شما اجازه می دهد تا داده های فیلم مانند عناوین، پوسترها و توضیحات را مستقیماً از پایگاه داده OMDB دریافت کند. این برای یک برنامه مرتبط با فیلم مانند MovieMaster ضروری است، زیرا اطلاعات به روز را بدون نیاز به ذخیره تمام داده ها برای کاربران فراهم می کند.
در یک PWA، استفاده از یک API مانند OMDB تضمین می کند که برنامه می تواند محتوای تازه را به کاربران ارائه دهد، حتی زمانی که بر روی دستگاه های آنها نصب شده باشد. همراه با آپشن های حافظه پنهان و آفلاین PWA، کاربران همچنان میتوانند جزئیات فیلمی را که قبلاً واکشی شدهاند مشاهده کنند، حتی اگر اتصال اینترنت را از دست بدهند.
توجه : مطمئن شوید که Node.js و npm روی سیستم شما نصب شده باشند. اگر آنها نیستند، می توانید آنها را از nodejs.org دانلود کنید.
نمای کلی ساختار پروژه
در اینجا یک نمای کلی از طرح پروژه آورده شده است:
/public : حاوی فایل های ثابت مانند تصاویر و فاویکون ها است.
/src/app : فایلهای برنامه اصلی شامل استایلهای جهانی (globals.css)، صفحه اصلی ( page.tsx )، تنظیمات طرحبندی ( layout.tsx ) و منطق سمت سرویس گیرنده ( RootLayoutClient.tsx ) را در خود جای میدهد.
/src/components : شامل اجزای قابل استفاده مجدد است. اجزای Shadcn UI در پوشه /ui قرار دارند و سایر اجزای خاص مانند MovieCard.tsx در اینجا هستند.
/src/lib : شامل توابع ابزار و کدهای واکشی داده ها، مانند fetchMovies.ts و useMediaQuery.ts است.
برای یک ظاهر طراحی، از موارد زیر استفاده می کنیم:
TailwindCSS : از طریق globals.css برای اولین رویکرد کاربردی برای طراحی اعمال می شود.
Shadcn UI : کتابخانه ای که اجزای رابط کاربری قابل دسترس و آماده برای استفاده را ارائه می دهد.
درک Layouts
این پروژه از دو طرح بندی کلیدی استفاده می کند:
layout.tsx : رندر سمت سرور را مدیریت می کند و متادیتای برنامه را تنظیم می کند. از مولفه RootLayoutClient
برای مدیریت عملکرد سمت سرویس گیرنده استفاده می کند. در اینجا به نظر می رسد:
import React from "react" ; import type { Metadata } from "next" ; import { cn } from "@/lib/utils" ; import { Inter as FontSans } from "next/font/google" ; import RootLayoutClient from "./RootLayoutClient" ; const fontSans = FontSans({ subsets : [ "latin" ], variable : "--font-sans" , }); export const metadata: Metadata = { title : "MovieMaster" , description : "MovieMaster PWA helps you find the latest movies with an easy search by genre, year, and more. It works smoothly on any device, even offline, giving you a great movie browsing experience." , manifest : "/web.manifest" , }; export default function RootLayout ( { children }: { children: React.ReactNode } ) { return ( < html lang = "en" suppressHydrationWarning > < body className = {cn( " min-h-screen bg-background font-sans antialiased ", fontSans.variable )}> < RootLayoutClient > {children} </ RootLayoutClient > </ body > </ html > ); }
RootLayoutClient.tsx : منطق سمت کلاینت را کنترل می کند که برای رندر کردن عناصر تعاملی و مدیریت حالت های رابط کاربری ضروری است. "
"use client" ; import React from "react" ; import { Toaster } from "@/components/ui/sonner" ; import "./globals.css" ; export default function RootLayoutClient ( { children }: { children: React.ReactNode } ) { return ( < div className = "text-white flex flex-col" > < div className = "container mx-auto px-4 max-w-[1024px]" > {children} < Toaster /> </ div > </ div > ); }
اجرای و پیش نمایش پروژه
برای شروع کار با پروژه خود:
سرور توسعه را راه اندازی کنید : در ترمینال خود، اجرا کنید:
npm run dev
با این کار سرور توسعه راه اندازی می شود و می توانید برنامه را با رفتن به http://localhost:3000 در مرورگر خود مشاهده کنید.
چگونه برنامه وب خود را به PWA تبدیل کنیم
برای تبدیل برنامه وب خود به PWA، معیارهای خاصی وجود دارد که برنامه شما باید آنها را رعایت کند. بیایید این الزامات را طی کنیم و تغییرات لازم را مرحله به مرحله اجرا کنیم.
معیارهای PWA
سرویس از طریق HTTPS : برنامه شما باید از طریق یک منبع امن (HTTPS) یا localhost
برای توسعه ارائه شود. اگر به صورت محلی در حال توسعه هستید، این معیار قبلاً برآورده شده است.
فایل مانیفست وب : یک فایل مانیفست وب، ابردادههایی را درباره برنامه شما، مانند نام، نمادها و URL شروع ارائه میکند. این فایل برای نصب برنامه شما بر روی دستگاه کاربر بسیار مهم است.
Service Worker با یک رویداد fetch
: برنامه شما باید یک سرویس دهنده را با حداقل یک رویداد fetch
ثبت کند. ثبت یک سرویس کار با حداقل یک رویداد واکشی برای اینکه برنامه شما به عنوان PWA شناخته شود و قابل نصب باشد، ضروری است. فراتر از آن، سرویسکاران عملکرد و قابلیت اطمینان برنامه شما را افزایش میدهند و به آن اجازه میدهند منابع را در حافظه پنهان نگه دارد و درخواستهای شبکه را حتی در حالت آفلاین مدیریت کند.
چگونه یک فایل مانیفست وب را به برنامه Next.js خود اضافه کنید
برای گفت ن یک فایل مانیفست وب در برنامه Next.js خود، آن را در فهرست عمومی/ دایرکتوری قرار دهید و در فایل طرح بندی خود به آن ارجاع دهید. مطمئن شوید که تمام تصاویری که در فایل مانیفست خود قرار می دهید در پوشه عمومی/ دایرکتوری نیز هستند.
نمونه ای از فایل web.manifest در اینجا آمده است:
{ "name" : "Movie Master" , "short_name" : "Moviemaster" , "theme_color" : "#8936FF" , "background_color" : "#333333" , "start_url" : "/" , "id" : "MovieMaster" , "display" : "standalone" , "description" : "MovieMaster PWA helps you find the latest movies with an easy search by genre, year, and more. It works smoothly on any device, even offline, giving you a great movie browsing experience." , "icons" : [ { "purpose" : "maskable" , "sizes" : "512x512" , "src" : "icon512_maskable.png" , "type" : "image/png" }, { "purpose" : "any" , "sizes" : "512x512" , "src" : "icon512_rounded.png" , "type" : "image/png" } ], "screenshots" : [ { "src" : "screenshot1.png" , "type" : "image/png" , "sizes" : "1080x1920" , "form_factor" : "narrow" } ] }
فیلدهای مورد نیاز
name
: نام کامل برنامه شما.
short_name
: نسخه کوتاهتری از نام برنامه، زمانی که فضای کافی برای نام کامل وجود ندارد نمایش داده میشود.
icons
: نمادهایی که برنامه شما را در اندازه های مختلف نشان می دهند.
start_url
: نشانی اینترنتی که با راه اندازی برنامه باز می شود.
display
: حالت نمایش را تعریف می کند (به عنوان مثال، standalone
برای یک تجربه تمام صفحه).
فیلدهای توصیه شده
theme_color
: رنگ تم رابط کاربری مرورگر را مانند نوار آدرس تنظیم می کند. این رنگ حس بومی PWA شما را افزایش می دهد.
این مثال نشان میدهد که چگونه رنگ تم (#8936FF) روی رابط کاربری مرورگر اعمال میشود و به PWA شما یک حس بومی میدهد.
که «انتقامجویان» (2012) را نشان میدهد که نشان میدهد چگونه رنگ تم بر روی رابط کاربری مرورگر اعمال میشود." width="2588" height="802" loading="lazy">
background_color
: رنگ پسزمینه صفحه نمایش را هنگام راهاندازی برنامه شما مشخص میکند.
screenshots
: برای بهبود تجربه نصب، بهویژه در دستگاههای Android، تصاویری از برنامه خود ارائه دهید.
این مثال نشان میدهد که چگونه اسکرینشاتها در طول فرآیند نصب نمایش داده میشوند و تجربه کاربر را بهویژه در دستگاههای اندرویدی بهبود میبخشند.
که نحوه نمایش اسکرین شات ها را در طول مراحل نصب در اندروید نشان می دهد." width="571" height="1280" loading="lazy">
id
: شناسه منحصر به فرد برای برنامه
نحوه ارجاع به فایل مانیفست وب
در مرحله بعد، بیایید فایل مانیفست را به صفحات شما اضافه کنیم. در Next.js، میتوانید آن را در metadata
Layout.tsx خود قرار دهید:
export const metadata: Metadata = { title : "MovieMaster" , description : "Find the latest movies with ease." , manifest : "/web.manifest" , // Link to the manifest file };
نحوه ثبت نام کارگر خدماتی
Service Worker اسکریپتی است که مرورگر شما در پسزمینه اجرا میکند و به شما امکان میدهد برنامهتان چگونه درخواستهای شبکه، حافظه پنهان و سایر وظایف را کنترل کند.
ثبت یک سرویس کار با حداقل یک رویداد fetch
برای اینکه برنامه شما به عنوان PWA شناخته شود و قابل نصب باشد، ضروری است.
یک فایل service-worker.js در پوشه public/ با کد زیر ایجاد کنید:
self.addEventListener( 'install' , ( event ) => { console .log( 'Service Worker installing.' ); }); self.addEventListener( 'activate' , ( event ) => { console .log( 'Service Worker activating.' ); }); self.addEventListener( 'fetch' , ( event ) => { console .log( 'Fetching:' , event.request.url); event.respondWith(fetch(event.request)); });
سپس، Service Worker را در فایل RootLayoutClient.tsx خود ثبت کنید:
"use client" ; import React from "react" ; export default function RootLayoutClient ( { children } ) { React.useEffect( () => { if ( "serviceWorker" in navigator) { navigator.serviceWorker .register( "/service-worker.js" ) .then( ( registration ) => { console .log( "Service Worker registered with scope:" , registration.scope); }) .catch( ( error ) => { console .error( "Service Worker registration failed:" , error); }); } }, []); return ( < div className = "text-white flex flex-col" > < div className = "container mx-auto px-4 max-w-[1024px]" > {children} </ div > </ div > ); }
هنگامی که برنامه شما تمام معیارها را برآورده می کند، کاربران می توانند به راحتی آن را روی دستگاه های خود نصب کنند. به عنوان مثال، هنگام استفاده از مرورگر Edge، یک گزینه نصب در منوی مرورگر ظاهر می شود که به کاربران اجازه می دهد برنامه شما را مستقیماً به دسکتاپ یا صفحه اصلی خود اضافه کنند.
در اینجا مراحل نصب به نظر می رسد:
که گزینه نصب را در مرورگر Edge نشان می دهد" width="1600" height="949" loading="lazy">
نحوه اضافه کردن پشتیبانی آفلاین
در این مرحله، اگرچه برنامه ما از نظر فنی یک PWA است، اما همچنان مانند یک برنامه وب معمولی عمل می کند. هر زمان که کاربر منبعی را درخواست می کند، برنامه درخواست شبکه می کند و در صورت عدم موفقیت شبکه، کاربر با صفحه خطا مواجه می شود. این ایده آل نیست، به خصوص زمانی که قدرت یک PWA در توانایی آن برای عملکرد آفلاین یا در شرایط شبکه ضعیف است.
با یک PWA، میتوانید هر درخواستی را که توسط برنامهتان ارسال میشود با استفاده از یک سرویسکار رهگیری کنید. این به شما انعطاف پذیری می دهد تا تصمیم بگیرید که چگونه محتوا را ارائه دهید - از شبکه یا از حافظه پنهان. این کنترل به شما امکان می دهد اطمینان حاصل کنید که کاربران همچنان می توانند حتی بدون اتصال به اینترنت به برنامه دسترسی داشته باشند.
نحوه ارائه منابع از شبکه
بیایید با نگاهی به نحوه عملکرد برنامه ما در حال حاضر، که مشابه هر برنامه وب استانداردی است، شروع کنیم:
self.addEventListener( "fetch" , ( event ) => { event.respondWith(fetch(event.request)); });
این کد به سادگی منابع را مستقیماً از شبکه واکشی می کند. اگر شبکه در دسترس نباشد، درخواست با شکست مواجه می شود و منجر به خطا می شود. این رفتار پیش فرض برای یک برنامه وب استاندارد است.
نحوه پیاده سازی پشتیبانی آفلاین
برای ارائه یک تجربه آفلاین، باید منابع برنامه خود را زمانی که کاربر آنلاین است، کش کنیم و سپس زمانی که کاربر آفلاین است، این منابع ذخیره شده را در حافظه پنهان ارائه کنیم. برای این کار از Cache Storage API استفاده می کنیم که به ما امکان می دهد منابع را به صورت محلی در دستگاه کاربر ذخیره کنیم.
چه چیزی را کش کنیم؟
تصمیم گیری در مورد حافظه پنهان بستگی به نیازهای برنامه شما دارد. برای یک برنامه جستجوی فیلم مانند برنامه ما، میخواهیم منابع ضروری مورد نیاز برای ارائه نسخه اصلی برنامه را در حافظه پنهان ذخیره کنیم:
صفحه اصلی HTML
شیوه نامه های CSS برای ارائه سایت مورد نیاز است
تصاویر استفاده شده در رابط کاربری
فایل های جاوا اسکریپت برای عملکرد مورد نیاز است
پاسخ های درخواست API
توجه: در حالی که می توانید تقریباً هر چیزی را در حافظه پنهان ذخیره کنید، به محدودیت های ذخیره سازی توجه داشته باشید، زیرا همه موارد ذخیره شده در حافظه پنهان در دستگاه کاربر ذخیره می شوند. از فضای ذخیره سازی عاقلانه استفاده کنید تا فضای زیادی را اشغال نکنید.
چه زمانی کش؟
هنگامی که ما می دانیم چه چیزی را در حافظه پنهان کنیم، نکته بعدی که باید در نظر بگیریم این است که چه زمانی را در حافظه پنهان کنیم. آیا باید همه چیز را در حین نصب سرویسکار ذخیره کنید یا باید منابع را همانطور که درخواست میکنید ذخیره کنید؟
پاسخ به نیازهای برنامه بستگی دارد، اما یک تمرین خوب این است که فایل های اصلی مورد نیاز برای ارائه نسخه اولیه برنامه را در حین نصب سرویس دهنده ذخیره کنید.
در اینجا نحوه انجام این کار آمده است:
const CACHE_NAME = "MOVIE_MASTER_V1" ; async function cacheCoreAssets ( ) { const cache = await caches.open(CACHE_NAME); return cache.addAll([ "/" , "/imdb-logo.svg" , "/rotten-tomatoes-logo.svg" , ]); } self.addEventListener( "install" , ( event ) => { event.waitUntil(cacheCoreAssets()); self.skipWaiting(); });
در این کد، self.skipWaiting()
تضمین میکند که سرویسکار جدید بلافاصله پس از نصب فعال میشود و مرحله انتظار را دور میزند.
همچنین مهم است که حافظه پنهان قدیمی را هنگام فعال شدن یک سرویسکار جدید حذف کنید:
async function clearOldCaches ( ) { const cacheNames = await caches.keys(); return Promise .all( cacheNames .filter( ( name ) => name !== CACHE_NAME) .map( ( name ) => caches.delete(name)) ); } self.addEventListener( "activate" , ( event ) => { event.waitUntil(clearOldCaches()); self.clients.claim(); });
متد self.clients.claim()
تضمین میکند که سرویسکار جدید کنترل تمام صفحات را به محض فعالسازی در دست میگیرد.
حافظه پنهان پویا
حافظه پنهان پویا مخصوصاً برای برنامههای React مانند ما که فایلهای استاتیک به طور خودکار تولید میشوند، مفید است. با کش پویا، نیازی به دانستن همه فایل ها از قبل ندارید. در عوض، در صورت درخواست فایلها، فرآیند ذخیرهسازی را برای شما انجام میدهد.
async function dynamicCaching ( request ) { const cache = await caches.open(CACHE_NAME); try { const response = await fetch(request); const responseClone = response.clone(); await cache.put(request, responseClone); return response; } catch (error) { console .error( "Dynamic caching failed:" , error); return caches.match(request); } }
با حافظه پنهان پویا، فایلهای درخواستی برنامه هنگام واکشی در حافظه پنهان ذخیره میشوند و اطمینان حاصل میشود که برای استفاده آفلاین در آینده در دسترس هستند.
ذخیره درخواست های API
هنگامی که صحبت از ذخیره پاسخ های API به میان می آید، به جای کش کردن کل پاسخ، اغلب بهتر است داده های خاصی که توسط API برگردانده شده است را در حافظه پنهان ذخیره کنید. برای این کار، میتوانیم از IndexedDB، یک پایگاه داده محلی ساخته شده در مرورگر استفاده کنیم.
IndexedDB قدرتمندتر از Cache Storage API است، به ویژه برای ذخیره و بازیابی داده های ساختار یافته مانند JSON. این باعث میشود که برای برنامههایی که نیاز به ذخیره دادههای پیچیده یا مدیریت کارآمد مقادیر زیادی از اطلاعات دارند، گزینهای عالی باشد.
که ساختار و داده های ذخیره شده در IndexedDB برای MovieMaster PWA را نشان می دهد." width="1600" height="1027" loading="lazy">
نحوه راه اندازی IndexedDB
ابتدا یک تابع برای باز کردن پایگاه داده و ایجاد یک فروشگاه شی ایجاد کنید:
const DB_NAME = "MovieMaster" ; const DB_VERSION = 1 ; const DB_STORE_NAME = "myStore" ; function openDb ( ) { return new Promise ( ( resolve, reject ) => { const request = indexedDB.open(DB_NAME, DB_VERSION); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); request.onupgradeneeded = ( event ) => { const db = event.target.result; db.createObjectStore(DB_STORE_NAME, { keyPath : "url" }); }; }); }
در مرحله بعد، توابعی برای گفت ن داده به پایگاه داده و بازیابی داده ها از پایگاه داده ایجاد کنید:
async function addData ( url, jsonData ) { const db = await openDb(); const transaction = db.transaction(DB_STORE_NAME, "readwrite" ); const store = transaction.objectStore(DB_STORE_NAME); const data = { url, response : JSON .stringify(jsonData), }; const request = store.put(data); await new Promise ( ( resolve, reject ) => { request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); } async function getData ( url ) { try { const db = await openDb(); const transaction = db.transaction(DB_STORE_NAME, "readonly" ); const store = transaction.objectStore(DB_STORE_NAME); const request = store.get(url); const result = await new Promise ( ( resolve, reject ) => { request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); if (result && result.response) { return JSON .parse(result.response); } return null ; } catch (error) { console .error( "Error retrieving from IndexedDB:" , error); return null ; } }
نحوه خدمت رسانی به منابع ذخیره شده
هنگامی که داراییهای خود را کش کردیم و دادههای API را در IndexedDB ذخیره کردیم، گام بعدی این است که این دادهها را زمانی که کاربران آفلاین هستند ارائه دهیم. چندین استراتژی برای رسیدن به این هدف وجود دارد:
کش اول استراتژی
در استراتژی cache-first، تحلیل می کنیم که آیا منبعی در کش موجود است یا خیر. در صورت وجود، ما آن را از حافظه پنهان خدمت می کنیم. اگر نه، آن را از شبکه دریافت می کنیم. این به ویژه برای ارائه دارایی های ثابت مانند فایل های HTML، CSS و جاوا اسکریپت مفید است:
async function cacheFirstStrategy ( request ) { try { const cache = await caches.open(CACHE_NAME); const cachedResponse = await cache.match(request); if (cachedResponse) { return cachedResponse; } const networkResponse = await fetch(request); const responseClone = networkResponse.clone(); await cache.put(request, responseClone); return networkResponse; } catch (error) { console .error( "Cache first strategy failed:" , error); return caches.match( "/offline" ); } } self.addEventListener( "fetch" , ( event ) => { const { request } = event; if (event.request.mode === "navigate" ) { event.respondWith(cacheFirstStrategy(request)); } else { event.respondWith(dynamicCaching(request)); } });
در این راهاندازی، استراتژی cache-first هنگام پیمایش به صفحات جدید اعمال میشود، در حالی که کش پویا سایر درخواستها را مدیریت میکند.
استراتژی شبکه اول
استراتژی شبکه اول برعکس است: ابتدا سعی می کند منابع را از شبکه واکشی کند، و اگر شبکه در دسترس نباشد، به حافظه پنهان باز می گردد. این استراتژی به ویژه برای درخواستهای API که در آن بهروزترین دادهها را میخواهید مفید است:
async function networkFirstStrategy ( request ) { try { const networkResponse = await fetch(request); if (networkResponse.ok) { const responseClone = networkResponse.clone(); const responseData = await responseClone.json(); await addData(request.url, responseData); return networkResponse; } throw new Error ( "Network response was not ok" ); } catch (error) { console .error( "Network first strategy failed:" , error); const cachedResponse = await getData(request.url); if (cachedResponse) { console .log( "Using cached response:" , cachedResponse); return new Response( JSON .stringify(cachedResponse), { status : 200 , headers : { "Content-Type" : "application/json" }, }); } return new Response( "[]" , { status : 200 }); } } self.addEventListener( "fetch" , ( event ) => { const { request } = event; const url = new URL(request.url); if (url.origin === "https://www.omdbapi.com" ) { event.respondWith(networkFirstStrategy(request)); } else if (event.request.mode === "navigate" ) { event.respondWith(cacheFirstStrategy(request)); } else { event.respondWith(dynamicCaching(request)); } });
در برنامه ما، از استراتژی شبکه اول برای تماسهای API استفاده میکنیم، و اطمینان میدهیم که کاربر آخرین دادهها را در حالت آنلاین دریافت میکند، در حالی که در حالت آفلاین به دادههای کش در IndexedDB بازمیگردد.
کد کارگر خدمات کامل
در اینجا فایل کامل service-worker.js است که شامل همه چیزهایی است که در مورد آن صحبت کردیم:
const CACHE_NAME = "MOVIE_MASTER_V1" ; const DB_NAME = "MovieMaster" ; const DB_VERSION = 1 ; const DB_STORE_NAME = "myStore" ; async function cacheCoreAssets ( ) { const cache = await caches.open(CACHE_NAME); return cache.addAll([ "/" , "/imdb-logo.svg" , "/rotten-tomatoes-logo.svg" , "/offline" , ]); } self.addEventListener( "install" , ( event ) => { event.waitUntil(cacheCoreAssets()); self.skipWaiting(); }); async function clearOldCaches ( ) { const cacheNames = await caches.keys(); return Promise .all( cacheNames .filter( ( name ) => name !== CACHE_NAME) .map( ( name ) => caches.delete(name)) ); } self.addEventListener( "activate" , ( event ) => { event.waitUntil(clearOldCaches()); self.clients.claim(); }); async function dynamicCaching ( request ) { const cache = await caches.open(CACHE_NAME); try { const response = await fetch(request); const responseClone = response.clone(); await cache.put(request, responseClone); return response; } catch (error) { console .error( "Dynamic caching failed:" , error); return caches.match(request); } } function openDb ( ) { return new Promise ( ( resolve, reject ) => { const request = indexedDB.open(DB_NAME, DB_VERSION); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); request.onupgradeneeded = ( event ) => { const db = event.target.result; db.createObjectStore(DB_STORE_NAME, { keyPath : "url" }); }; }); } async function addData ( url, jsonData ) { const db = await openDb(); const transaction = db.transaction(DB_STORE_NAME, "readwrite" ); const store = transaction.objectStore(DB_STORE_NAME); const data = { url, response : JSON .stringify(jsonData), }; const request = store.put(data); await new Promise ( ( resolve, reject ) => { request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); } async function getData ( url ) { try { const db = await openDb(); const transaction = db.transaction(DB_STORE_NAME, "readonly" ); const store = transaction.objectStore(DB_STORE_NAME); const request = store.get(url); const result = await new Promise ( ( resolve, reject ) => { request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); if (result && result.response) { return JSON .parse(result.response); } return null ; } catch (error) { console .error( "Error retrieving from IndexedDB:" , error); return null ; } } async function cacheFirstStrategy ( request ) { try { const cache = await caches.open(CACHE_NAME); const cachedResponse = await cache.match(request); if (cachedResponse) { return cachedResponse; } const networkResponse = await fetch(request); const responseClone = networkResponse.clone(); await cache.put(request, responseClone); return networkResponse; } catch (error) { console .error( "Cache first strategy failed:" , error); return caches.match( "/offline" ); } } async function networkFirstStrategy ( request ) { try { const networkResponse = await fetch(request); if (networkResponse.ok) { const responseClone = networkResponse.clone(); const responseData = await responseClone.json(); await addData(request.url, responseData); return networkResponse; } throw new Error ( "Network response was not ok" ); } catch (error) { console .error( "Network first strategy failed:" , error); const cachedResponse = await getData(request.url); if (cachedResponse) { console .log( "Using cached response:" , cachedResponse); return new Response( JSON .stringify(cachedResponse), { status : 200 , headers : { "Content-Type" : "application/json" }, }); } return new Response( "[]" , { status : 200 }); } } self.addEventListener( "fetch" , ( event ) => { const { request } = event; const url = new URL(request.url); if (url.origin === "https://www.omdbapi.com" ) { event.respondWith(networkFirstStrategy(request)); } else if (event.request.mode === "navigate" ) { event.respondWith(cacheFirstStrategy(request)); } else { event.respondWith(dynamicCaching(request)); } });
با این راهاندازی، PWA شما اکنون به طور کامل برای مدیریت محتوای استاتیک و پویا، ارائه یک تجربه آفلاین و ذخیره دادههای API به صورت هوشمند مجهز شده است.
ادامه مطلب
استراتژی ها و تفاوت های ظریف بیشتری برای ایجاد یک تجربه آفلاین قوی با کارکنان خدمات وجود دارد. اگر میخواهید عمیقتر به این موضوع بپردازید، بیشتر در مورد این موضوع بخوانید:
استراتژی های مختلف ذخیره سازی: Cache-First، Network-First، Stale-While-Revalidate و غیره.
آپشن های سرویسکار پیشرفته مانند همگامسازی پسزمینه و اعلانهای فشاری.
بهترین روش ها برای مدیریت حافظه نهان و محدودیت های ذخیره سازی
با درک و پیاده سازی این مفاهیم، می توانید اطمینان حاصل کنید که برنامه شما حتی در شرایط چالش برانگیز شبکه، کاربردی و کاربرپسند باقی می ماند.
ارائه یک صفحه بازگشتی
حتی با وجود استراتژیهای کش، ممکن است مواقعی پیش بیاید که کاربران سعی کنند به منبعی دسترسی پیدا کنند که به صورت آفلاین و در شبکه در دسترس نیست. برای رسیدگی به این موقعیتها، میتوانیم یک صفحه بازگشتی ایجاد کنیم. هر زمان که کاربر سعی کند به محتوایی دسترسی پیدا کند که از حافظه پنهان یا شبکه قابل بازیابی نیست، این صفحه نشان داده می شود.
اگر پروژه نمونه را برای این آموزش کلون کرده اید، از قبل باید یک صفحه بازگشتی در فهرست برنامه داشته باشید. این صفحه به گونه ای طراحی شده است که سناریوهای آفلاین را به خوبی مدیریت کند و شامل یک بازی ساده Tic-Tac-Toe است تا کاربران بتوانند در حین انتظار برای بازیابی اتصال، بازی کنند. صفحه بازگشتی به این صورت است:
"use client" ; import TicTacToe from "@/components/TicTacToe" ; import { useState, useEffect } from "react" ; import { useRouter } from "next/navigation" ; import Link from "next/link" ; const Fallback: React.FC = () => { const [isOnline, setIsOnline] = useState( false ); const router = useRouter(); useEffect( () => { const handleOnline = () => { setIsOnline( true ); // Redirect to homepage if online router.push( "/" ); }; const handleOffline = () => { setIsOnline( false ); }; window .addEventListener( "online" , handleOnline); window .addEventListener( "offline" , handleOffline); return () => { window .removeEventListener( "online" , handleOnline); window .removeEventListener( "offline" , handleOffline); }; }, [router]); const handleRefresh = () => { if (navigator.onLine) { router.push( "/" ); } else { setIsOnline( false ); } }; return ( < div className = "flex mx-auto h-screen max-w-[500px] w-full flex-col items-center justify-center h-screen bg-foreground text-black p-6 mt-12 text-white" > < h1 className = "text-3xl font-bold mb-6" > {isOnline ? "You are online!" : "You are offline"} </ h1 > < p className = "text-lg text-center mb-6" > {isOnline ? "You are back online." : "Please check your internet connection and try again."} </ p > < div className = "" > < TicTacToe /> </ div > {isOnline ? ( < Link href = { "/"} className = "mt-6 px-4 py-2 bg-blue-500 text-white rounded shadow hover:bg-blue-600" > Return to Homepage </ Link > ) : ( < button onClick = {handleRefresh} className = "mt-6 px-4 py-2 bg-blue-500 text-white rounded shadow hover:bg-blue-600" > Refresh </ button > )} </ div > ); }; export default Fallback;
که صفحه بازگشتی ما را نشان می دهد" width="1600" height="1195" loading="lazy">
توجه: میتوانید این صفحه بازگشتی را مطابق با نیازهای برنامه خود سفارشی کنید، چه نمایش محتوای آفلاین مفید، ارائه پیام، یا ارائه یک ویژگی تعاملی کوچک مانند بازی Tic-Tac-Toe که در اینجا گنجانده شده است.
کش کردن صفحه بازگشتی
در مرحله بعد، مطمئن شوید که صفحه بازگشتی هنگام نصب سرویسدهنده ذخیره میشود:
const CACHE_NAME = "MOVIE_MASTER_V1" ; async function cacheCoreAssets ( ) { const cache = await caches.open(CACHE_NAME); return await cache.addAll([ "/" , "/fallback" , // other assets ]); }
ارائه صفحه بازگشتی
در نهایت، cacheFirstStrategy
تغییر دهید تا در صورت عدم موفقیت درخواست، صفحه offline.html را ارائه دهد:
async function cacheFirstStrategy ( request ) { try { const cache = await caches.open(CACHE_NAME); const cachedResponse = await cache.match(request); if (cachedResponse) { return cachedResponse; } const networkResponse = await fetch(request); const responseClone = networkResponse.clone(); await cache.put(request, responseClone); return networkResponse; } catch (error) { console .error( "Cache first strategy failed:" , error); return caches.match( "/offline.html" ); } }
این رویکرد تضمین میکند که کاربران همیشه در زمان آفلاین بودن یا در دسترس نبودن یک منبع، به جای خطا، پیامی معنیدار ببینند.
نتیجه گیری
با راهاندازی برنامه Next.js، آن را با موفقیت به یک برنامه وب پیشرفته (PWA) کاملاً کاربردی تبدیل کردهایم و آن را بهتر و کاربرپسندتر میکنیم.
این راهنما نشان داد که چگونه می توان با استفاده از Next.js با گفت ن ویژگی هایی مانند پشتیبانی آفلاین، کش کردن، و کارگران سرویس، یک PWA قوی ساخت. این پیشرفتها با ترکیب بهترین برنامههای وب و بومی، عملکرد را افزایش میدهند و تجربهای روان را در همه دستگاهها ارائه میکنند.
با این نکات ، شما آماده خواهید بود تا PWA های جذاب ، قابل اعتماد و با کارایی بالا را ایجاد کنید که در توسعه وب برجسته هستند.
ارسال نظر