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'lerle örnek bir web uygulamasına anında gezinme ve sorunsuz sayfa geçişleri eklemeyi öğreneceksiniz.

Ö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 oluşturulur.

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 işleme, geri-ileri önbellek (bfcache) ve Private Prefetch Proxy'yi, sorunsuz sayfa geçişleri için ise kök/paylaşılan öğe geçişlerini uyguluyorsunuz. Çok sayfalı uygulama sayfaları için önceden işleme ve geri-ileri önbellek, tek sayfalı uygulama sayfaları için ise paylaşılan öğe geçişleri uygularsınız.

Site hızı, kullanıcı deneyiminin her zaman önemli bir yönü olmuştur. Bu nedenle Google, gerçek dünyadaki kullanıcı deneyimini ölçmek için web sayfalarının yükleme performansını, etkileşimini ve görsel kararlılığını ölçen bir metrik grubu olan Core Web Vitals'ı kullanıma sundu. En yeni API'ler, web sitenizin alandaki Core Web Vitals puanını (özellikle yükleme performansı için) iyileştirmenize yardımcı olur.

Bfcache'in yükleme süresini nasıl iyileştirdiğini gösteren demo resmi

Mindvalley'den demo

Kullanıcılar, mobil uygulamalarda gezinme ve durum değişikliklerini son derece sezgisel hale getirmek için geçişlerin kullanımına da alışkındır. Maalesef bu tür kullanıcı deneyimlerinin web'de kopyalanması kolay değildir. Mevcut web platformu API'leriyle benzer efektler elde edebilirsiniz ancak özellikle Android veya iOS uygulamalarındaki benzer özelliklerle karşılaştırıldığında geliştirme süreci çok zor veya karmaşık olabilir. Sorunsuz API'ler, uygulama ve web arasındaki bu kullanıcı ve geliştirici deneyimi boşluğunu doldurmak için tasarlanmıştır.

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

pixiv ve Tokopedia'dan demolar

Ön koşullar

Aşağıdakiler hakkında bilgi sahibi olunmalıdır:

Öğrenecekleriniz:

Nasıl uygulanır?

  • Önceden işleme
  • bfcache
  • Private Prefetch Proxy
  • Kök/paylaşılan öğe geçişleri

Ne oluşturacaksınız?

En yeni anlık ve sorunsuz tarayıcı özellikleriyle zenginleştirilmiş, Next.js ile oluşturulmuş örnek bir web uygulaması:

  • Önceden oluşturma ile neredeyse anında gezinme
  • Tarayıcının geri ve ileri düğmeleriyle anında yükleme için bfcache
  • Private Prefetch Proxy veya imzalı takas (SXG) ile kaynaklar arası gezinmede mükemmel ilk izlenimler
  • Kök/paylaşılan öğe geçişiyle sayfalar arasında sorunsuz geçiş

İhtiyacınız olanlar

  • Chrome 101 veya sonraki sürümler

2. Başlayın

Chrome flag'lerini etkinleştirme

  1. about://flags adresine gidin ve Prerender2 ile 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 en sevdiğiniz geliştirme ortamında 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. Sunucuyu 3000 numaralı bağlantı noktasında başlatın:
npm run dev
  1. Tarayıcınızda http://localhost:3000 adresine gidin.

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

3. Önceden işleme özelliğini entegre etme

Bu demoda, örnek uygulamadaki sebze ayrıntıları sayfasının yüklenme süresi, sunucu tarafındaki rastgele bir gecikme nedeniyle çok yavaştır. Önceden oluşturma ile bu bekleme süresini ortadan kaldırabilirsiniz.

Sebze listesi sayfasına önceden oluşturma düğmeleri eklemek ve kullanıcının tıklamasının ardından önceden oluşturma işlemini tetiklemelerine izin vermek için:

  1. speculation-rules komut dosyası etiketini dinamik olarak ekleyen 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. PrerenderButton bileşenini list-item.js dosyasında 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.

SpeculationRules bileşeni, uygulama prerenderURL durumunu güncellediğinde 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. Önceden oluştur'u tıklayın.

Artık yükleme hızında önemli bir iyileşme olduğunu görebilirsiniz. Gerçek kullanım alanında, önceden oluşturma, kullanıcının bir sonraki ziyaret etme olasılığı olan sayfa için bazı sezgisel yöntemlerle tetiklenir.

Önceden oluşturma için örnek uygulama demo videosu

Analytics

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

Bu sorunu düzeltmek için document.prerendering ve prerenderingchange etkinliğini kullanıma sunuyoruz:

  • 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 oluşturma ile uyumlu olacak şekilde başarıyla değiştirdiniz. Artık tarayıcı konsolunda sayfa görüntüleme günlüklerini doğru zamanlamayla görebilirsiniz.

4. bfcache engelleyicilerini kaldırma

unload etkinlik işleyicisini kaldırma

Gereksiz bir unload etkinliğinin olması, artık önerilmeyen çok yaygın bir hatadır. Bu durum, geri-ileri önbelleğin çalışmasını engellemekle kalmaz, aynı zamanda güvenilir de değildir. Örneğin, mobil cihazlarda ve Safari'de her zaman tetiklenmez.

unload etkinliği yerine, unload etkinliği tetiklendiğinde ve bir sayfa geri-ileri önbelleğe yerleştirildiğinde tüm durumlarda 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')
})

Önbellek kontrolü üst bilgisini güncelleme

Cache-control: no-store HTTP üstbilgisiyle sunulan sayfalar, tarayıcının bfcache özelliğinden yararlanmaz. Bu nedenle, bu üstbilgiyi dikkatli kullanmak iyi bir uygulamadır. Özellikle sayfa, oturum açma durumu gibi kişiselleştirilmiş veya kritik bilgiler içermiyorsa büyük olasılıkla Cache-control: no-store HTTP üstbilgisiyle sunmanız gerekmez.

Örnek uygulamanın önbellek kontrolü üstbilgisini 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/vegetables/[name].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'ten geri yüklenip yüklenmediğini belirleme

pageshow etkinliği, sayfa ilk yüklendiğinde ve sayfa geri-ileri önbellekten her geri yüklendiğinde load etkinliğinden hemen sonra tetiklenir. pageshow etkinliğinde, sayfa bfcache'ten geri yüklenmişse doğru, yüklenmemişse yanlış olan bir persisted özelliği bulunur. Normal sayfa yüklemelerini geri/ileri önbellek geri yüklemelerinden ayırt etmek için persisted özelliğini kullanabilirsiniz. Başlıca analiz hizmetleri bfcache'i tanımalıdır ancak sayfanın bfcache'ten geri yüklenip yüklenmediğini kontrol edebilir ve etkinlikleri manuel olarak gönderebilirsiniz.

Bir sayfanın bfcache'ten 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 sayfasında hata ayıklama

Chrome Geliştirici Araçları, sayfalarınızın bfcache için optimize edildiğinden emin olmak üzere test etmenize 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 Önbelleği > Testi Çalıştır'ı tıklayın.

Chrome Geliştirici Araçları, sayfanın geri-ileri önbellekten geri yüklenip yüklenemeyeceğini belirlemek için sayfadan ayrılmaya ve ardından geri dönmeye çalışır.

49bf965af35d5324.png

Başarılı olursa panelde sayfanın geri-ileri önbellekten geri yüklendiği belirtilir:

47015a0de45f0b0f.png

Başarısız olursa panelde sayfanın geri yüklenmediği ve bunun nedeni belirtilir. Nedeni geliştirici olarak ele alabileceğiniz bir durumsa panelde bu da belirtilir.

dcf0312c3fc378ce.png

5. Siteler arası önceden getirmeyi etkinleştirme

Önceden getirme, kullanıcı gezinirken baytların tarayıcıda hazır olması için getirme işlemlerini erken başlatır. Bu da gezinmeyi hızlandırır. Bu, Core Web Vitals'ı iyileştirmenin ve gezinmeden önce bazı ağ etkinliklerini telafi etmenin kolay bir yoludur. Bu, Largest Contentful Paint (LCP) değerini doğrudan hızlandırır ve gezinme sırasında First Input Delay (FID) ile Cumulative Layout Shift (CLS) için daha fazla alan sağlar.

Private Prefetch Proxy, siteler arası önceden getirme özelliğini etkinleştirir ancak kullanıcıyla ilgili özel bilgileri hedef sunucuya göstermez.

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

Private Prefetch Proxy ile siteler arası önceden getirmeyi etkinleştirme

Web sitesi sahipleri, önceden getirme işlemini traffic-advice adlı iyi bilinen bir kaynak aracılığıyla kontrol eder. Bu kaynak, web tarayıcıları için /robots.txt'ye benzer ve bir HTTP sunucusunun, uygulayan aracıların ilgili tavsiyeyi uygulamasını bildirmesine olanak tanır. Şu anda web sitesi sahipleri, ağ bağlantılarına izin verilmemesi veya bağlantıların sınırlandırılması konusunda aracıya tavsiyede bulunabilir. Gelecekte başka tavsiyeler de eklenebilir.

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

  1. Aşağıdaki 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 Private Prefetch Proxy'nin gönderdiği istenen önceden getirmelerin oranını kontrol etmek için kullanılan bir alandır.

Trafik tavsiyesi, application/trafficadvice+json MIME türüyle döndürülmelidir.

  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 etme

Kullanıcı web'de bir sayfadan diğerine gittiğinde, ilk sayfa kaybolup yeni sayfa göründüğü için gördüğü içerik aniden ve beklenmedik bir şekilde değişir. Bu sıralı ve bağlantısız kullanıcı deneyimi, kullanıcının bulunduğu yere nasıl geldiğini anlamaya çalışmak zorunda kalması nedeniyle kafa karıştırıcıdır ve daha yüksek bir bilişsel yüke yol açar. Ayrıca bu deneyim, kullanıcıların istenen hedefin yüklenmesini beklerken sayfa yüklemesini ne kadar algıladığını artırır.

Sayfalar arasında gezinirken bağlamda kalabildikleri için kullanıcıların bilişsel yükünü azaltan sorunsuz yükleme animasyonları, bu sırada ilgi çekici ve keyifli bir şey gördükleri için yükleme gecikmesini de azaltır. Bu nedenlerle çoğu platform, geliştiricilerin sorunsuz geçişler oluşturmasına olanak tanıyan, kullanımı kolay temel işlevler sağlar (ör. Android, iOS, macOS ve Windows).

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

pixiv&#39;den 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 paylaşılan öğe geçişleri API'sini 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ğindeki transition.start() yöntemini tetiklemek için asenkron işlevi ç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 tamamlamak için usePageTransition() kancasını çağırın.

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

pages/fruits/[name].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 öğeleri ile animasyonu daha da süsleyebilirsiniz.

Paylaşılan öğe geçişi olmadan örnek uygulama demo videosu Paylaşılan öğe geçişi içeren örnek uygulama demo videosu

7. Tebrikler

Tebrikler! Düşük sürtünmeli, ilgi çekici ve sezgisel bir kullanıcı deneyimi sunan anlık ve sorunsuz bir web uygulaması oluşturdunuz.

Daha fazla bilgi

Önceden işleme

bfcache

Siteler arası önceden getirme

İmzalı takaslar

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

Bu API'ler henüz geliştirmenin ilk aşamalarında olduğundan lütfen geri bildiriminizi crbug.com adresinden veya ilgili API'lerin GitHub deposundaki sorunlar bölümünden paylaşın.