Web uygulamasına anında gezinme ve sorunsuz sayfa geçişleri ekleyin

1. Başlamadan önce

Bu codelab'de, Google Chrome'un yerel olarak desteklediği en yeni API'lere sahip örnek bir web uygulamasına anında gezinme ve sorunsuz sayfa geçişlerinin nasıl ekleneceği açıklanmaktadır.

Örnek web uygulaması, popüler meyve ve sebzelerin besin değerlerini kontrol eder. Meyve listesi ve meyve ayrıntıları sayfaları tek sayfalık uygulama (SPA) olarak, sebze listesi ve sebze ayrıntıları sayfaları ise geleneksel çok sayfalı uygulama (MPA) olarak geliştirildi.

Mobil cihazdaki örnek uygulama ekran görüntüsü Mobil cihazdaki örnek uygulama ekran görüntüsü

Özellikle, anında gezinme için önceden oluşturma, geri-ileri önbellek (bfcache) ve Özel Önceden Getirme Proxy'sini, sorunsuz sayfa geçişleri için de kök/paylaşılan öğe geçişlerini uygularsınız. MPA sayfaları için önceden işleme ve bfcache, SPA sayfaları için ise paylaşılan öğe geçişleri uygularsınız.

Site hızı, kullanıcı deneyiminin her zaman önemli bir unsurudur. Bu nedenle Google, gerçek dünyadaki kullanıcı deneyimini ölçmek için web sayfalarının yükleme performansını, etkileşimi ve görsel kararlılığını ölçen bir grup metrik olan Core Web Vitals'ı kullanıma sundu. En son API'ler, özellikle yük performansı açısından web sitenizin Core Web Vitals puanını sahada artırmanıza yardımcı olur.

bfcache'nin yükleme süresini nasıl iyileştirdiği demo resmi

Mindvalley'den tanıtım

Kullanıcılar, mobil yerel uygulamalarda gezinme ve durum değişikliklerinin son derece sezgisel olmasını sağlayan geçişler kullanmaya da alışkındır. Maalesef bu tür kullanıcı deneyimlerini web'de kopyalamak kolay değildir. Mevcut web platformu API'leriyle benzer etkiler elde edebilirsiniz ancak geliştirme, özellikle Android veya iOS uygulamalarındaki özellik benzerleriyle karşılaştırıldığında çok zor veya karmaşık olabilir. Kolay API'ler, uygulama ve web arasındaki bu kullanıcı ve geliştirici deneyimi boşluğunu doldurmak için tasarlanmıştır.

pixiv'deki Shared Element Transitions API demosu Tokopedia'dan Shared Element Transitions API demosu

pixiv ve Tokopedia'dan demolar

Ön koşullar

Şu konuda bilgi:

Öğrenecekleriniz:

Nasıl uygulanır?

  • Önceden işleme
  • önbellek
  • Özel Önceden Getirme Proxy'si
  • Kök/paylaşılan öğe geçişleri

Neler oluşturacaksınız?

En son hazır ve sorunsuz tarayıcı özellikleriyle zenginleştirilmiş, Next.js ile oluşturulmuş örnek bir web uygulaması:

  • Önceden işleme ile neredeyse anında gezinme
  • Tarayıcının geri ve ileri düğmeleriyle anında yüklemeler için bfcache
  • Özel Önceden Getirme Proxy'si veya imzalı takas (SXG) ile kaynaklar arası gezinmeden elde edilen çok iyi ilk gösterimler
  • Kök/paylaşılan öğeler geçişi olan sayfalar arasında sorunsuz geçiş

Gerekenler

  • Chrome 101 veya sonraki sürümler

2. Başlayın

Chrome flag'lerini etkinleştir

  1. about://flags sayfasına gidip Prerender2 ve documentTransition API çalışma zamanı işaretlerini etkinleştirin.
  2. Tarayıcınızı yeniden başlatın.

Kodu alın

  1. Bu GitHub deposundaki kodu, favori geliştirme ortamınızda açın:
git clone -b codelab git@github.com:googlechromelabs/instant-seamless-demo.git
  1. Sunucuyu çalıştırmak için gereken bağımlılıkları yükleyin:
npm install
  1. 3000 numaralı bağlantı noktasından sunucuyu başlatın:
npm run dev
  1. Tarayıcınızda http://localhost:3000 adresine gidin.

Artık uygulamanızı düzenleyebilir ve iyileştirebilirsiniz. Her değişiklik yaptığınızda uygulama yeniden yüklenir ve değişiklikleriniz doğrudan görünür.

3. Önceden işlemeyi entegre etme

Bu demoda, örnek uygulamadaki bitki ayrıntıları sayfasının yükleme süresi, sunucu tarafındaki rastgele bir gecikme nedeniyle çok yavaştır. Önceden işleme ile bu bekleme süresini ortadan kaldırırsınız.

Sebze listesi sayfasına önceden işleme düğmeleri eklemek ve kullanıcı tıkladıktan sonra önceden işlemeyi tetiklemelerine izin vermek için:

  1. speculation-rules komut dosyası etiketini dinamik bir şekilde yerleştiren bir düğme bileşeni oluşturun:

components/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>
  )
}
  1. list-item.js dosyasındaki PrerenderButton bileşenini içe aktarın.

components/list-item.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>
  )
}
  1. Spekülasyon Kuralları API'si eklemek için bir bileşen oluşturun.

Uygulama, prerenderURL durumunu güncellediğinde SpeculationRules bileşeni, sayfaya dinamik olarak bir komut dosyası etiketi ekler.

components/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])
}
  1. Bileşenleri uygulamayla entegre edin.

pages/_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
  1. Prerender'ı (Önceden oluştur) tıklayın.

Yüklemede önemli iyileşmeler görebilirsiniz. Gerçek kullanım örneğinde, önceden işleme bazı buluşsal yöntemler tarafından kullanıcının ziyaret etme olasılığı bulunan sayfa için tetiklenir.

Önceden işleme için örnek uygulama tanıtım videosu

Analiz

Varsayılan olarak, DOMContentLoaded etkinliği gerçekleştiğinde örnek web uygulamasındaki analytics.js dosyası bir sayfa görüntüleme etkinliği gönderir. Bu etkinlik önceden işleme aşamasında tetiklendiği için maalesef bu mantıklı değildir.

Bu sorunu düzeltmek amacıyla bir document.prerendering ve prerenderingchange etkinliğini tanıtmak için:

  • analytics.js dosyasını yeniden yazın:

public/analytics.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()
  })
  ...

Harika. Analizlerinizi, önceden işlemeyle uyumlu olacak şekilde başarıyla değiştirdiniz. Artık tarayıcı konsolunda doğru zamanlamayla sayfa görüntüleme günlüklerini görebilirsiniz.

4. Bfcache engelleyicileri kaldırın

unload etkinlik işleyicisini kaldır

Gereksiz bir unload etkinliğinin olması, artık önerilmez ve çok yaygın olarak yapılan bir hatadır. Bu, yalnızca bfcache'in çalışmasını önlemekle kalmaz, aynı zamanda güvenilir değildir. Örneğin, mobil cihazlarda ve Safari'de her zaman tetiklenmez.

unload etkinliği yerine, unload etkinliğinin tetiklendiği her durumda ve bir sayfa bfcache'ye yerleştirildiğinde tetiklenen pagehide etkinliğini kullanırsınız.

unload etkinlik işleyicisini kaldırmak için:

  • analytics.js dosyasında, unload etkinlik işleyicisinin kodunu pagehide etkinlik işleyicisinin koduyla değiştirin:

public/analytics.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')
})

Cache-control başlığını güncelleme

Cache-control: no-store HTTP başlığı ile sunulan sayfalar tarayıcının bfcache özelliğinden yararlanamaz. Bu nedenle bu başlıkla ilgili tutumlu olmak iyi bir uygulamadır. Özellikle, sayfada oturum açma durumu gibi kişiselleştirilmiş veya önemli bilgiler yoksa sayfayı muhtemelen Cache-control: no-store HTTP başlığıyla sunmanız gerekmez.

Örnek uygulamanın önbellek kontrolü başlığını güncellemek için:

  • getServerSideProps kodunu değiştirin:

pages/vegetables/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')
  ...

pages/sebzeler/[ad].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')
  ...

Bir sayfanın bfcache'den geri yüklenip yüklenmediğini belirleyin

pageshow etkinliği, sayfa ilk yüklendiğinde load etkinliğinden hemen sonra ve sayfa bfcache'den geri yüklendiğinde tetiklenir. pageshow etkinliğinin persisted özelliği vardır. Bu özellik, sayfa bfcache'den geri yüklendiyse doğru, değilse false (yanlış) değerini alır. Normal sayfa yüklemelerini bfcache geri yüklemelerinden ayırt etmek için persisted özelliğini kullanabilirsiniz. Önde gelen analiz hizmetleri bfcache'den haberdar olmalıdır. Ancak sayfanın bfcache'den geri yüklenip geri yüklenmediğini kontrol edebilir ve etkinlikleri manuel olarak gönderebilirsiniz.

Bir sayfanın bfcache'den geri yüklenip yüklenmediğini belirlemek için:

  • Bu kodu analytics.js dosyasına ekleyin.

public/analytics.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()
    }
  })

Web sayfalarında hata ayıklama

Chrome Geliştirici Araçları, sayfalarınızın bfcache için optimize edildiğinden emin olmak ve uygun olmamalarına neden olabilecek sorunları belirlemenize yardımcı olabilir.

Belirli bir sayfayı test etmek için:

  1. Chrome'da sayfaya gidin.
  2. Chrome Geliştirici Araçları'nda Uygulama > Geri-İleri Önbellek > Testi Çalıştırın.

Chrome Geliştirici Araçları, sayfanın bfcache'den geri yüklenip yüklenemeyeceğini belirlemek için sayfadan çıkıp daha sonra geri dönmeyi dener.

49bf965af35d5324.png

İşlem başarılı olursa panel size sayfanın geri-ileri önbellekten geri yüklendiğini bildirir:

47015a0de45f0b0f.png

Başarısız olmanız durumunda panelde, sayfanın geri yüklenmediği ve bunun nedeni belirtilir. Bu durumun nedeni geliştirici olarak ele alabileceğiniz bir konuysa panel bunu da belirtir.

dcf0312c3fc378ce.png

5. Siteler arası önceden getirme özelliğini etkinleştir

Önceden getirme işlemi erken başlar. Böylece, kullanıcı gezindiğinde baytlar zaten tarayıcıda yer alır. Bu da gezinmeyi hızlandırır. Bu, Core Web Vitals'ı iyileştirmenin ve bazı ağ etkinliklerini gezinmeden önce dengelemenin kolay bir yoludur. Bu, Largest Contentful Paint (LCP) değerini doğrudan hızlandırır ve gezinme sonrasında First Input Delay (FID) ve Cumulative Layout Shift (CLS) için daha fazla yer açar.

Özel Önceden Getirme Proxy'si, siteler arası önceden getirme özelliğini etkinleştirir ancak kullanıcıyla ilgili gizli bilgileri hedef sunucuya ifşa etmez.

Özel Önceden Getirme Proxy&#39;sinin işleyiş şekli

Özel Önceden Getirme Proxy'si ile siteler arası önceden getirmeyi etkinleştirme

Web sitesi sahipleri, web tarayıcıları için /robots.txt benzeri, iyi bilinen bir trafik tavsiyesi kaynağı aracılığıyla önceden getirme kontrolünü elinde tutar. Bu kaynak, bir HTTP sunucusunun, aracıların ilgili tavsiyeyi uygulaması gerektiğini bildirmesine olanak tanır. Şu anda web sitesi sahipleri, temsilciye ağ bağlantılarına izin vermemesini veya bağlantıları kısmasını önerebilir. Gelecekte başka öneriler de eklenebilir.

Trafik tavsiyesi kaynağı barındırmak için:

  1. Şu JSON benzeri dosyayı ekleyin:

public/.well-known/traffic-advice

[
  {
    "user_agent": "prefetch-proxy",
    "google_prefetch_proxy_eap": {
      "fraction": 1
    }
  }
]

google_prefetch_proxy_eap alanı, erken erişim programı için özel bir alandır. fraction alanı ise Özel Önceden Getirme Proxy'sinin gönderdiği istenen önceden getirmelerin oranını kontrol eden bir alandır.

Trafik önerisi application/trafficadvice+json MIME türüyle döndürülür.

  1. next.config.js dosyasında yanıt başlığını yapılandırın:

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

6. Shared Element Transitions API'yi entegre edin

Kullanıcı web'de bir sayfadan bir başkasına geçtiğinde, ilk sayfa kaybolup yeni sayfa görüntülenirken kullanıcının gördüğü içerik aniden ve beklenmedik bir şekilde değişir. Bu sıralı, bağlantısız kullanıcı deneyimi kafa karıştırıcıdır ve kullanıcı bulunduğu yere nasıl gideceğini bir araya getirmek zorunda kalır. Bu da, bilişsel yüklenme düzeyinin artmasına yol açar. Ayrıca bu deneyim, kullanıcıların istenen hedefin yüklenmesini beklerken sayfanın yüklendiğini algılama oranını artırır.

Sorunsuz yükleme animasyonları, kullanıcılar sayfalar arasında gezinirken bağlam içinde kaldığı için bilişsel yükü azaltır ve aynı zamanda ilgi çekici ve keyifli bir şey gördüğü için yüklemenin algılanan gecikmesini azaltır. Bu nedenle çoğu platform, geliştiricilerin sorunsuz geçişler oluşturmasına olanak tanıyan, kullanımı kolay temel öğeler sağlar (ör. Android, iOS, MacOS ve Windows).

Shared Element Transitions API, geçişlerin dokümanlar arası (MPA) veya doküman içi (SPA) olmasına bakılmaksızın, geliştiricilere web'de aynı özelliği sunar.

pixiv&#39;deki Shared Element Transitions API demosu Tokopedia&#39;dan Shared Element Transitions API demosu

pixiv ve Tokopedia'dan demolar

Örnek uygulamanın SPA bölümü için Shared Element Transitions API'yi entegre etmek üzere:

  1. use-page-transition.js dosyasındaki geçişi yönetmek için özel bir kanca oluşturun:

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
}
  1. Liste sayfasında usePageTransitionPrep() özel kancasını çağırın ve ardından click etkinliği içinde transition.start() yöntemini tetiklemek için eş zamansız işlevini çağırın.

İşlevin içinde shared-element sınıf öğeleri toplanır ve paylaşılan öğeler olarak kaydedilir.

components/list-item.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>
  )
}
  1. Ayrıntılar sayfasında, transition.start() geri çağırma işlevini bitirmek için usePageTransition() kancasını çağırın.

Bu geri çağırmada, ayrıntı sayfasındaki paylaşılan öğeler de kaydedilir.

sayfalar/meyveler/[ad].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>
  )
...
}

Artık resim öğelerinin liste ve ayrıntı sayfalarında paylaşıldığını ve sayfa geçişinde sorunsuz bir şekilde bağlandığını görebilirsiniz. Hatta CSS sözde öğeleriyle daha ilgi çekici hale getirmek için animasyonu özelleştirebilirsiniz.

Shared Element Transition&#39;ın kullanılmadığı örnek uygulama tanıtım videosu Shared Element Transition&#39;ın kullanıldığı örnek uygulama tanıtım videosu

7. Tebrikler

Tebrikler! Kolayca, ilgi çekici ve sezgisel bir kullanıcı deneyimi sunan hazır ve sorunsuz bir web uygulaması oluşturdunuz.

Daha fazla bilgi

Önceden işleme

önbellek

Siteler arası önceden getirme

İmzalı takaslar

Kök/paylaşılan öğe geçişleri

Bu API'ler hâlâ geliştirmenin ilk aşamalarında olduğundan geri bildiriminizi lütfen crbug.com adresinden veya sorun olarak ilgili API'lerin GitHub deposunda paylaşın.