۱. قبل از شروع
این آزمایشگاه کد به شما آموزش میدهد که چگونه با جدیدترین APIهایی که گوگل کروم به صورت پیشفرض پشتیبانی میکند، ناوبری فوری و انتقال یکپارچه صفحات را به یک برنامه وب نمونه اضافه کنید.
این برنامه وب نمونه، ارزش غذایی میوهها و سبزیجات محبوب را بررسی میکند. صفحات فهرست میوهها و جزئیات میوهها به صورت یک برنامه تک صفحهای (SPA) و صفحات فهرست سبزیجات و جزئیات سبزیجات به صورت یک برنامه چند صفحهای سنتی (MPA) ساخته شدهاند.


به طور خاص، شما پیشرندرینگ ، حافظه پنهان عقب/جلو (bfcache) و پروکسی پیشواکشی خصوصی را برای ناوبری فوری و انتقال عناصر ریشه/مشترک را برای انتقالهای یکپارچه صفحه پیادهسازی میکنید. شما پیشرندرینگ و bfcache را برای صفحات MPA و انتقال عناصر مشترک را برای صفحات SPA پیادهسازی میکنید.
سرعت سایت همیشه جنبه مهمی از تجربه کاربری است، به همین دلیل گوگل Core Web Vitals را معرفی کرد، مجموعهای از معیارها که عملکرد بارگذاری، تعامل و پایداری بصری صفحات وب را برای سنجش تجربه کاربری در دنیای واقعی اندازهگیری میکنند. جدیدترین APIها به شما کمک میکنند تا امتیاز Core Web Vitals وبسایت خود را در این زمینه، به ویژه برای عملکرد بارگذاری، بهبود بخشید.

نسخه نمایشی از Mindvalley
کاربران همچنین به استفاده از انتقالها برای ایجاد ناوبری و تغییرات وضعیت بسیار شهودی در برنامههای بومی موبایل عادت کردهاند. متأسفانه، تکرار چنین تجربیات کاربری در وب ساده نیست. در حالی که ممکن است بتوانید با APIهای فعلی پلتفرم وب به جلوههای مشابهی دست یابید، توسعه ممکن است بسیار دشوار یا پیچیده باشد، به خصوص در مقایسه با همتایان ویژگی در برنامههای اندروید یا iOS. APIهای یکپارچه برای پر کردن این شکاف تجربه کاربر و توسعهدهنده بین برنامه و وب طراحی شدهاند.


نسخههای نمایشی از pixiv و Tokopedia
پیشنیازها
آگاهی از:
- اچتیامال
- سیاساس
- جاوا اسکریپت
- ابزارهای توسعهدهندگان گوگل کروم
آنچه یاد خواهید گرفت:
نحوه پیاده سازی:
- پیشرندرینگ
- بی اف کش
- پروکسی خصوصی Prefetch
- انتقال عناصر ریشه/مشترک
آنچه خواهید ساخت
یک برنامه وب نمونه ساخته شده با Next.js که با جدیدترین قابلیتهای مرورگر فوری و یکپارچه غنی شده است:
- ناوبری تقریباً آنی با پیشرندرینگ
- bfcache برای بارگذاری فوری با دکمههای عقب و جلو مرورگر
- برداشتهای اولیه عالی از ناوبری بین مبدا با پروکسی خصوصی پیش واکشی یا تبادل امضا شده (SXG)
- انتقال یکپارچه بین صفحات با انتقال عناصر ریشه/مشترک
آنچه نیاز دارید
- نسخه کروم ۱۰۱ یا بالاتر
۲. شروع کنید
فعال کردن پرچمهای کروم
- به about://flags بروید و سپس پرچمهای زمان اجرای
Prerender2وdocumentTransition APIرا فعال کنید. - مرورگر خود را مجدداً راه اندازی کنید.
کد را دریافت کنید
- کد را از این مخزن گیتهاب در محیط توسعه مورد علاقه خود باز کنید:
git clone -b codelab git@github.com:googlechromelabs/instant-seamless-demo.git
- وابستگیهای مورد نیاز برای اجرای سرور را نصب کنید:
npm install
- سرور را روی پورت ۳۰۰۰ شروع کنید:
npm run dev
- در مرورگر خود به آدرس http://localhost:3000 بروید.
حالا میتوانید برنامه خود را ویرایش و بهبود دهید. هر زمان که تغییراتی ایجاد کنید، برنامه دوباره بارگذاری میشود و تغییرات شما مستقیماً قابل مشاهده هستند.
۳. ادغام پیشرندرینگ
برای این دمو، زمان بارگذاری صفحه جزئیات سبزیجات در برنامه نمونه به دلیل تأخیر دلخواه در سمت سرور بسیار کند است. شما میتوانید این زمان انتظار را با پیشرندرینگ از بین ببرید.
برای افزودن دکمههای پیشرندر به صفحه فهرست سبزیجات و فعال کردن پیشرندر پس از کلیک کاربر:
- یک کامپوننت دکمه ایجاد کنید که تگ اسکریپت «specution-rules» را به صورت پویا وارد کند:
کامپوننتها/prerender-button.js
import { useContext } from 'react'
import ResourceContext from './resource-context'
// You use resource context to manage global states.
// In the PrerenderButton component, you update the prerenderURL parameter when the button is clicked.
export default function PrerenderButton() {
const { dispatch } = useContext(ResourceContext)
const handleClick = (e) => {
e.preventDefault()
e.stopPropagation()
const parent = e.target.closest('a')
if (!parent) {
return
}
const href = parent.getAttribute('href')
dispatch({ type: 'update', prerenderURL: href })
}
return (
<button className='ml-auto bg-gray-200 hover:bg-gray-300 px-4 rounded' onClick={handleClick}>
Prerender
</button>
)
}
- کامپوننت
PrerenderButtonرا در فایلlist-item.jsوارد کنید.
کامپوننتها/لیست-آیتم.js
// Codelab: Add a PrerenderButton component.
import PrerenderButton from './prerender-button'
...
function ListItemForMPA({ item, href }) {
return (
<a href={href} className='block flex items-center'>
<Icon src={item.image} />
<div className='text-xl'>{item.name}</div>
{/* Codelab: Add PrerenderButton component. */}
<PrerenderButton />
</a>
)
}
- یک کامپوننت برای افزودن API قوانین حدس و گمان ایجاد کنید.
کامپوننت SpeculationRules به صورت پویا یک تگ اسکریپت را در صفحه وارد میکند، زمانی که برنامه وضعیت prerenderURL را بهروزرسانی میکند.
کامپوننتها/speculationrules.js
import Script from 'next/script'
import { useContext, useMemo } from 'react'
import ResourceContext from './resource-context'
export default function SpeculationRules() {
const { state } = useContext(ResourceContext)
const { prerenderURL } = state
return useMemo(() => {
return (
<>
{prerenderURL && (
<Script id='speculationrules' type='speculationrules'>
{`
{
"prerender":[
{
"source": "list",
"urls": ["${prerenderURL}"]
}
]
}
`}
</Script>
)}
</>
)
}, [prerenderURL])
}
- کامپوننتها را با برنامه ادغام کنید.
صفحات/_app.js
// Codelab: Add the SpeculationRules component.
import SpeculationRules from '../components/speculationrules'
function MyApp({ Component, pageProps }) {
useAnalyticsForSPA()
return (
<ResourceContextProvider>
<Layout>
<Component {...pageProps} />
</Layout>
{/* Codelab: Add SpeculationRules component */}
<SpeculationRules />
<Script id='analytics-for-mpa' strategy='beforeInteractive' src='/analytics.js' />
</ResourceContextProvider>
)
}
export default MyApp
- روی پیشرندر کلیک کنید.
حالا میتوانید بهبود قابل توجه بارگذاری را مشاهده کنید. در حالت واقعی، پیشرندرینگ برای صفحهای که کاربر احتمالاً در مرحله بعد از آن بازدید خواهد کرد، طبق برخی از روشهای اکتشافی، فعال میشود.

تجزیه و تحلیل
به طور پیشفرض، فایل analytics.js در برنامه وب نمونه، هنگامی که رویداد DOMContentLoaded رخ میدهد، یک رویداد page-view ارسال میکند. متأسفانه، این کار عاقلانه نیست زیرا این رویداد در مرحله پیش رندرینگ اجرا میشود.
برای رفع این مشکل ، رویدادهای document.prerendering و prerenderingchange را معرفی کنید:
- فایل
analytics.jsرا بازنویسی کنید:
عمومی/تحلیلی.js
const sendEvent = (type = 'pageview') => {
// Codelab: Make analytics prerendering compatible.
// The pageshow event could happen in the prerendered page before activation.
// The prerendered page should be handled by the prerenderingchange event.
if (document.prerendering) {
return
}
console.log(`Send ${type} event for MPA navigation.`)
fetch(`/api/analytics?from=${encodeURIComponent(location.pathname)}&type=${type}`)
}
...
// Codelab: Make analytics prerendering compatible.
// The prerenderingchange event is triggered when the page is activated.
document.addEventListener('prerenderingchange', () => {
console.log('The prerendered page was activated.')
sendEvent()
})
...
عالی شد، شما با موفقیت تجزیه و تحلیلهای خود را طوری تغییر دادید که با پیشرندر سازگار باشند. حالا میتوانید گزارشهای بازدید از صفحه را با زمانبندی صحیح در کنسول مرورگر مشاهده کنید.
۴. مسدودکنندههای bfcache را حذف کنید
کنترل کننده رویداد unload را حذف کنید
داشتن یک رویداد unload غیرضروری یک اشتباه بسیار رایج است که دیگر توصیه نمیشود. این کار نه تنها مانع از کار bfcache میشود، بلکه غیرقابل اعتماد نیز هست. برای مثال، همیشه روی موبایل و سافاری اجرا نمیشود.
به جای رویداد unload ، از رویداد pagehide استفاده میکنید که در تمام مواردی که رویداد unload اجرا میشود و زمانی که صفحهای در bfcache قرار میگیرد، فعال میشود.
برای حذف کنترل کننده رویداد unload :
- در فایل
analytics.js، کد مربوط به رویدادunloadرا با کد مربوط به رویدادpagehideجایگزین کنید:
عمومی/تحلیلی.js
// Codelab: Remove the unload event handler for bfcache.
// The unload event handler prevents the content from being stored in bfcache. Use the pagehide event instead.
window.addEventListener('pagehide', () => {
sendEvent('leave')
})
بهروزرسانی هدر کنترل حافظه پنهان
صفحاتی که با هدر HTTP از نوع Cache-control: no-store ارائه میشوند، از ویژگی bfcache مرورگر بهرهمند نمیشوند، بنابراین بهتر است در استفاده از این هدر صرفهجویی کنید. به طور خاص، اگر صفحه حاوی اطلاعات شخصیسازی شده یا حیاتی مانند وضعیت ورود به سیستم نباشد، احتمالاً نیازی به ارائه آن با هدر HTTP از نوع Cache-control: no-store ندارید.
برای بهروزرسانی هدر cache-control برنامه نمونه:
- کد
getServerSidePropsرا تغییر دهید:
صفحات/سبزیجات/index.js
export const getServerSideProps = middleware(async (ctx) => {
const { req, res } = ctx
// Codelab: Modify the cache-control header.
res.setHeader('Cache-Control', 'public, s-maxage=10, stale-while-revalidate=59')
...
صفحات/سبزیجات/[نام].js
export const getServerSideProps = middleware(async (ctx) => {
const { req, res, query } = ctx
// Codelab: Modify the cache-control header.
res.setHeader('Cache-Control', 'public, s-maxage=10, stale-while-revalidate=59')
...
تعیین اینکه آیا یک صفحه از bfcache بازیابی شده است یا خیر
رویداد pageshow درست پس از رویداد load ، زمانی که صفحه برای اولین بار بارگذاری میشود و هر زمان که صفحه از bfcache بازیابی شود، اجرا میشود. رویداد pageshow دارای یک ویژگی persisted است که اگر صفحه از bfcache بازیابی شده باشد، مقدار آن true و در غیر این صورت false است. میتوانید از ویژگی persisted برای تشخیص بارگذاریهای منظم صفحه از بازیابیهای bfcache استفاده کنید. سرویسهای تحلیلی بزرگ باید از bfcache آگاه باشند، اما میتوانید بررسی کنید که آیا صفحه از bfcache بازیابی شده است یا خیر و رویدادها را به صورت دستی ارسال کنید.
برای تعیین اینکه آیا یک صفحه از bfcache بازیابی شده است یا خیر:
- این کد را به فایل
analytics.jsاضافه کنید.
عمومی/تحلیلی.js
// Codelab: Use the pageshow event handler for bfcache.
window.addEventListener('pageshow', (e) => {
// If the persisted flag exists, the page was restored from bfcache.
if (e.persisted) {
console.log('The page was restored from bfcache.')
sendEvent()
}
})
اشکالزدایی یک صفحه وب
ابزارهای توسعهدهنده کروم میتوانند به شما کمک کنند صفحات خود را آزمایش کنید تا از بهینهسازی آنها برای bfcache اطمینان حاصل کنید و هرگونه مشکلی را که ممکن است آنها را غیرقابل قبول کند، شناسایی کنید.
برای آزمایش یک صفحه خاص:
- در کروم به صفحه مورد نظر بروید.
- در ابزارهای توسعهدهنده کروم، روی برنامه > حافظه پنهان رو به جلو > اجرای تست کلیک کنید.
ابزارهای توسعهدهنده کروم تلاش میکنند تا به صفحه دیگری بروند و سپس برگردند تا مشخص کنند که آیا صفحه میتواند از طریق bfcache بازیابی شود یا خیر.

در صورت موفقیت، پنل به شما میگوید که صفحه از حافظه پنهان back-forward بازیابی شده است:

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

۵. فعال کردن پیشواکشی بین سایتی
پیشواکشی (Prefetching) شروع به واکشی (fetch) زودتر میکند تا بایتها هنگام پیمایش کاربر در مرورگر باشند، که این امر پیمایش را تسریع میکند. این یک راه آسان برای بهبود Core Web Vitals و جبران برخی از فعالیتهای شبکه قبل از پیمایش است. این امر مستقیماً Largest Contentful Paint (LCP) را تسریع میکند و فضای بیشتری برای First Input Delay (FID) و Cumulative Layout Shift (CLS) هنگام پیمایش فراهم میکند.
پراکسی پیشواکشی خصوصی، پیشواکشی بین سایتی را فعال میکند، اما اطلاعات خصوصی کاربر را برای سرور مقصد فاش نمیکند.

فعال کردن پیشواکشی بین سایتی با استفاده از پراکسی پیشواکشی خصوصی
صاحبان وبسایت کنترل پیشواکشی را از طریق یک منبع توصیه ترافیک شناختهشده، مشابه /robots.txt برای خزندههای وب، حفظ میکنند که به یک سرور HTTP اجازه میدهد اعلام کند که عوامل پیادهسازی باید توصیههای مربوطه را اعمال کنند. در حال حاضر، صاحبان وبسایت میتوانند به عامل توصیه کنند که اتصالات شبکه را مجاز نداند یا محدود کند. در آینده، ممکن است توصیههای دیگری نیز اضافه شود.
برای میزبانی یک منبع مشاوره ترافیکی:
- این فایل شبیه به JSON را اضافه کنید:
public/.well-known/traffic-advice
[
{
"user_agent": "prefetch-proxy",
"google_prefetch_proxy_eap": {
"fraction": 1
}
}
]
فیلد google_prefetch_proxy_eap یک فیلد ویژه برای برنامه دسترسی زودهنگام است و فیلد fraction فیلدی برای کنترل کسری از prefetch های درخواستی است که Private Prefetch Proxy ارسال میکند.
توصیههای ترافیکی باید با نوع MIME application/trafficadvice+json برگردانده شوند.
- در فایل
next.config.js، هدر پاسخ را پیکربندی کنید:
فایل پیکربندی بعدی (next.config.js)
const nextConfig = {
// Codelab: Modify content-type for traffic advice file.
async headers() {
return [
{
source: '/.well-known/traffic-advice',
headers: [
{
key: 'Content-Type',
value: 'application/trafficadvice+json',
},
],
},
]
},
}
module.exports = nextConfig
۶. ادغام API انتقال عناصر مشترک
وقتی کاربری در وب از یک صفحه به صفحه دیگر میرود، محتوایی که میبیند به طور ناگهانی و غیرمنتظره تغییر میکند، صفحه اول ناپدید میشود و صفحه جدید ظاهر میشود. این تجربه کاربری متوالی و منقطع، گیجکننده است و منجر به بار شناختی بالاتری میشود، زیرا کاربر مجبور میشود چگونگی رسیدن به جایی که هست را کنار هم بگذارد. علاوه بر این، این تجربه، میزان درک کاربران از بارگذاری صفحه را در حین انتظار برای بارگذاری مقصد مورد نظر، افزایش میدهد.
انیمیشنهای بارگذاری روان، بار شناختی را کاهش میدهند زیرا کاربران هنگام پیمایش بین صفحات، در متن باقی میمانند و تأخیر ادراکشده هنگام بارگذاری را کاهش میدهند زیرا کاربران در این بین چیزی جذاب و لذتبخش میبینند. به همین دلایل، اکثر پلتفرمها، مانند اندروید، iOS، MacOS و ویندوز، ابتداییهای آسان برای استفاده را ارائه میدهند که به توسعهدهندگان اجازه میدهد انتقالهای یکپارچهای ایجاد کنند.
API مربوط به انتقال عناصر مشترک، صرف نظر از اینکه انتقالها بین سندی (MPA) یا درون سندی (SPA) باشند، همین قابلیت را در وب در اختیار توسعهدهندگان قرار میدهد.


نسخههای نمایشی از pixiv و Tokopedia
برای ادغام API انتقال عناصر مشترک برای بخش SPA برنامه نمونه:
- یک قلاب سفارشی برای مدیریت انتقال در فایل
use-page-transition.jsایجاد کنید:
utils/use-page-transition.js
import { useEffect, useContext, useRef, useCallback } from 'react'
import ResourceContext from '../components/resource-context'
// Call this hook on this first page before you start the page transition. For Shared Element Transitions, you need to call the transition.start() method before the next page begins to render, and you need to do the Document Object Model (DOM) modification or setting of new shared elements inside the callback so that this hook returns the promise and defers to the callback resolve.
export const usePageTransitionPrep = () => {
const { dispatch } = useContext(ResourceContext)
return (elm) => {
const sharedElements = elm.querySelectorAll('.shared-element')
// Feature detection
if (!document.createDocumentTransition) {
return null
}
return new Promise((resolve) => {
const transition = document.createDocumentTransition()
Array.from(sharedElements).forEach((elm, idx) => {
transition.setElement(elm, `target-${idx}`)
})
transition.start(async () => {
resolve()
await new Promise((resolver) => {
dispatch({ type: 'update', transition: { transition, resolver } })
})
})
})
}
}
// Call this hook on the second page. Inside the useEffect hook, you can refer to the actual DOM element and set them as shared elements with the transition.setElement() method. When the resolver function is called, the transition is initiated between the captured images and newly set shared elements.
export const usePageTransition = () => {
const { state, dispatch } = useContext(ResourceContext)
const ref = useRef(null)
const setRef = useCallback((node) => {
ref.current = node
}, [])
useEffect(() => {
if (!state.transition || !ref.current) {
return
}
const { transition, resolver } = state.transition
const sharedElements = ref.current.querySelectorAll('.shared-element')
Array.from(sharedElements).forEach((elm, idx) => {
transition.setElement(elm, `target-${idx}`)
})
resolver()
return () => {
dispatch({ type: 'update', transition: null })
}
})
return setRef
}
- هوک سفارشی
usePageTransitionPrep()را در صفحه لیست فراخوانی کنید و سپس تابع async را برای اجرای متدtransition.start()درون رویدادclickفراخوانی کنید.
درون تابع، عناصر کلاس shared-element جمعآوری و به عنوان عناصر مشترک ثبت میشوند.
کامپوننتها/لیست-آیتم.js
// Codelab: Add the Shared Element Transitions API.
import { usePageTransitionPrep } from '../utils/use-page-transition'
...
function ListItemForSPA({ item, href }) {
// Codelab: Add Shared Element Transitions.
const transitionNextState = usePageTransitionPrep()
const handleClick = async (e) => {
const elm = e.target.closest('a')
await transitionNextState(elm)
}
return (
<Link href={href}>
<a className='block flex items-center' onClick={handleClick}>
<Icon src={item.image} name={item.name} className='shared-element' />
<div className='text-xl'>{item.name}</div>
</a>
</Link>
)
}
- در صفحه جزئیات، هوک
usePageTransition()را فراخوانی کنید تا تابع فراخوانیtransition.start()به پایان برسد.
در این فراخوانی مجدد، عناصر مشترک در صفحه جزئیات نیز ثبت میشوند.
صفحات/میوهها/[نام].js
// Codelab: Add the Shared Element Transitions API.
import { usePageTransition } from '../../utils/use-page-transition'
const Item = ({ data }) => {
const { name, image, amountPer, nutrition } = data
// Codelab: Add the Shared Element Transitions API.
const ref = usePageTransition()
return (
<div className={'flex flex-col items-center justify-center py-4 px-4 sm:flex-row'} ref={ref}>
<div className='flex flex-col items-center sm:w-2/4'>
<Image
className='object-cover border-gray-100 border-2 rounded-full shared-element'
src={image}
width='240'
height='240'
alt={`picture of ${name}`}
/>
<h1 className='text-4xl font-bold mt-4'>{name}</h1>
</div>
<div className='sm:w-2/4 w-full'>
<Nutrition amountPer={amountPer} nutrition={nutrition} />
</div>
</div>
)
...
}
حالا میتوانید ببینید که عناصر تصویر در صفحات فهرست و جزئیات به اشتراک گذاشته شدهاند و در انتقال صفحه به طور یکپارچه به هم متصل شدهاند. شما حتی میتوانید انیمیشن را با استفاده از شبه عناصر CSS سفارشی کنید تا زیباتر شود.


۷. تبریک
تبریک میگویم! شما یک برنامه وب فوری و یکپارچه با یک تجربه کاربری کمدردسر، جذاب و شهودی ایجاد کردید.
بیشتر بدانید
پیشرندرینگ
بی اف کش
پیشواکشی بینسایتی
مبادلات امضا شده
انتقال عناصر ریشه/مشترک
این APIها هنوز در مراحل اولیه توسعه هستند، بنابراین لطفاً نظرات خود را در crbug.com یا به عنوان مشکل در مخزن Github APIهای مربوطه به اشتراک بگذارید.