1. Prima di iniziare
Questo codelab ti insegna come aggiungere una navigazione istantanea e transizioni di pagina perfette a un'app web di esempio con le API più recenti supportate in modo nativo da Google Chrome.
L'app web di esempio controlla i valori nutrizionali di frutta e verdura più comuni. Le pagine della lista della frutta e dei dettagli della frutta sono costruite come un'app a pagina singola (SPA), mentre le pagine dell'elenco di ortaggi e dei dettagli della frutta sono realizzate come un'app tradizionale a più pagine (MPA).
In particolare, implementerai il prerendering, la cache back/forward (bfcache) e il proxy di precaricamento privato per la navigazione istantanea e le transizioni di elementi principali/condivisi per le transizioni di pagina senza interruzioni. Vengono implementati il prerendering e la memorizzazione nella cache bfcache per le pagine MPA e le transizioni degli elementi condivisi per le pagine SPA.
La velocità del sito è sempre un aspetto importante dell'esperienza utente, motivo per cui Google ha introdotto Core Web Vitals, un insieme di metriche che misurano le prestazioni di caricamento, l'interattività e la stabilità visiva delle pagine web per valutare l'esperienza utente reale. Le API più recenti ti aiutano a migliorare il punteggio Core Web Vitals del tuo sito web sul campo, in particolare per le prestazioni di caricamento.
Demo di Mindvalley
Gli utenti sono inoltre abituati a usare le transizioni per rendere le navigazioni e le modifiche di stato estremamente intuitive nelle app native per dispositivi mobili. Purtroppo, la replica di queste esperienze utente non è semplice sul web. Sebbene sia possibile ottenere effetti simili con le attuali API web, lo sviluppo potrebbe essere troppo difficile o complesso, soprattutto se confrontato con le controparti delle funzionalità nelle app per Android o iOS. Le API senza interruzioni sono progettate per colmare questo divario nell'esperienza utente e sviluppatore tra le app e il web.
Prerequisiti
Conoscenza di:
- HTML
- CSS
- JavaScript
- Strumenti per sviluppatori di Google Chrome
Cosa imparerai:
Come implementare:
- Prerendering
- Bfcache
- Proxy di precaricamento privato
- Transizioni di elementi condivisi/principali
Cosa creerai
Un'app web di esempio creata con Next.js e arricchita con le funzionalità browser istantanee e senza interruzioni più recenti:
- Navigazione quasi istantanea con prerendering
- bfcache per caricamenti istantanei con i pulsanti Avanti e Indietro del browser
- Ottime prime impressioni dalla navigazione multiorigine con il proxy di precaricamento privato o Signed Exchange (SXG)
- Una transizione perfetta tra le pagine con la transizione degli elementi principali/condivisi
Che cosa ti serve
- Chrome 101 o versioni successive
2. Inizia
Abilita i flag di Chrome
- Vai all'indirizzo about://flags e abilita i flag di runtime
Prerender2
edocumentTransition API
. - Riavvia il browser.
Ottieni il codice
- Apri il codice da questo repository GitHub nel tuo ambiente di sviluppo preferito:
git clone -b codelab git@github.com:googlechromelabs/instant-seamless-demo.git
- Installa le dipendenze necessarie per eseguire il server:
npm install
- Avvia il server sulla porta 3000:
npm run dev
- Vai a http://localhost:3000 nel tuo browser.
Ora puoi modificare e migliorare la tua app. Ogni volta che apporti modifiche, l'app si ricarica e le modifiche sono direttamente visibili.
3. Integra il prerendering
Ai fini di questa demo, il tempo di caricamento della pagina dei dettagli vegetali nell'app di esempio è molto lento a causa di un ritardo arbitrario sul lato server. Questo tempo di attesa viene eliminato con il prerendering.
Per aggiungere pulsanti di prerendering alla pagina dell'elenco vegetale e consentire loro di attivare il prerendering dopo il clic dell'utente:
- Crea un componente del pulsante che inserisce dinamicamente il tag script delle regole di speculazione:
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>
)
}
- Importa il componente
PrerenderButton
nel filelist-item.js
.
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>
)
}
- Crea un componente per aggiungere l'API Speculation Rules.
Il componente SpeculationRules
inserisce dinamicamente un tag script nella pagina quando l'app aggiorna lo stato prerenderURL
.
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])
}
- Integra i componenti con l'app.
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
- Fai clic su Prerendering.
Ora puoi vedere il miglioramento significativo del caricamento. Nel caso d'uso reale, viene attivato il prerendering per la pagina che è probabile che l'utente visiti successivamente in base a una serie di euristiche.
Analytics
Per impostazione predefinita, il file analytics.js
nell'app web di esempio invia un evento di visualizzazione di pagina quando si verifica l'evento DOMContentLoaded
. Purtroppo, non è una buona strategia perché questo evento si attiva durante la fase di prerendering.
Per introdurre un evento document.prerendering
e prerenderingchange
in modo da risolvere il problema:
- Riscrivi il file
analytics.js
:
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()
})
...
Fantastico, hai modificato correttamente i tuoi dati e analisi in modo che siano compatibili con il prerendering. Ora puoi vedere i log delle visualizzazioni di pagina con la tempistica corretta nella console del browser.
4. Rimuovi i blocchi bfcache
Rimuovi il gestore di eventi unload
Avere un evento unload
non necessario è un errore molto comune e non più consigliato. Non solo impedisce il funzionamento di bfcache, ma è anche inaffidabile. Ad esempio, non sempre si attiva sui dispositivi mobili e su Safari.
Anziché un evento unload
, puoi utilizzare l'evento pagehide
, che si attiva in tutti i casi quando viene attivato l'evento unload
e quando una pagina viene inserita nella cache bfcache.
Per rimuovere il gestore di eventi unload
:
- Nel file
analytics.js
, sostituisci il codice del gestore di eventiunload
con il codice del gestore di eventipagehide
:
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')
})
Aggiorna l'intestazione Cache-control
Le pagine pubblicate con un'intestazione HTTP Cache-control: no-store
non beneficiano della funzionalità bfcache del browser, pertanto è consigliabile non utilizzare questa intestazione. In particolare, se la pagina non contiene informazioni personalizzate o critiche, ad esempio lo stato di accesso, probabilmente non è necessario pubblicarla con l'intestazione HTTP Cache-control: no-store
.
Per aggiornare l'intestazione cache-control dell'app di esempio:
- Modifica il codice
getServerSideProps
:
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')
...
pagine/verdure/[nome].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')
...
Determina se una pagina viene ripristinata da bfcache
L'evento pageshow
viene attivato subito dopo l'evento load
quando la pagina viene caricata inizialmente e ogni volta che la pagina viene ripristinata da bfcache. L'evento pageshow
ha una proprietà persisted
, che è vera se la pagina è stata ripristinata da bfcache e false in caso contrario. Puoi utilizzare la proprietà persisted
per distinguere i caricamenti di pagina regolari dai ripristini bfcache. I principali servizi di analisi dovrebbero conoscere bfcache, ma puoi verificare se la pagina viene ripristinata da bfcache e inviare gli eventi manualmente.
Per determinare se una pagina viene ripristinata da bfcache:
- Aggiungi questo codice al file
analytics.js
.
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()
}
})
Eseguire il debug di una pagina web
Gli Strumenti per sviluppatori di Chrome possono aiutarti a testare le tue pagine per assicurarti che siano ottimizzate per bfcache e per identificare eventuali problemi che potrebbero renderle non idonee.
Per testare una pagina specifica:
- Vai alla pagina in Chrome.
- Negli Strumenti per sviluppatori di Chrome, fai clic su Applicazione > Cache back-forward > Esegui test.
Gli Strumenti per sviluppatori di Chrome tentano di uscire dalla pagina e poi di tornare indietro per determinare se è possibile ripristinare la pagina da bfcache.
Se l'operazione ha esito positivo, il riquadro indica che la pagina è stata ripristinata dalla cache back-forward:
In caso contrario, il riquadro indica che la pagina non è stata ripristinata e il motivo del ripristino. Il riquadro te lo comunica anche se il motivo è un problema che puoi affrontare in qualità di sviluppatore.
5. Attiva precaricamento tra siti
Il precaricamento viene avviato prima, in modo che i byte siano già all'interno del browser quando l'utente naviga, il che accelera la navigazione. È un modo semplice per migliorare Core Web Vitals e compensare alcune attività di rete prima della navigazione. In questo modo si accelera direttamente la metrica Largest Contentful Paint (LCP) e si ottiene più spazio per First Input Delay (FID) e Cumulative Layout Shift (CLS) durante la navigazione.
Il proxy di precaricamento privato abilita il precaricamento tra siti, ma non rivela informazioni private sull'utente al server di destinazione.
Attiva il precaricamento tra siti con il proxy di precaricamento privato
I proprietari di siti web mantengono il controllo del precaricamento tramite una nota risorsa traffic-advice, analoga a /robots.txt
per i web crawler, che consente a un server HTTP di dichiarare che gli agenti di implementazione devono applicare il consiglio corrispondente. Attualmente, i proprietari di siti web possono consigliare all'agente di non consentire o limitare le connessioni di rete. In futuro potrebbero essere aggiunti altri consigli.
Per ospitare una risorsa di consulenza sul traffico:
- Aggiungi questo file di tipo JSON:
public/.well-known/traffic-advice
[
{
"user_agent": "prefetch-proxy",
"google_prefetch_proxy_eap": {
"fraction": 1
}
}
]
Il campo google_prefetch_proxy_eap
è un campo speciale per il programma di accesso in anteprima, mentre il campo fraction
è un campo per controllare la frazione dei precaricamenti richiesti che il proxy di precaricamento privato invia.
I consigli sul traffico devono essere restituiti con il tipo MIME application/trafficadvice+json
.
- Nel file
next.config.js
, configura l'intestazione della risposta:
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. API Integrate Shared Element Transiziones
Quando un utente naviga sul web da una pagina all'altra, i contenuti che vede cambiano improvvisamente e inaspettatamente quando la prima pagina scompare e appare la nuova. Questa esperienza utente in sequenza e disconnessa è disorientante e determina un carico cognitivo più elevato perché l'utente è costretto a mettere insieme come è arrivato dove si trova. Inoltre, questa esperienza aumenta la percezione del caricamento della pagina da parte degli utenti mentre attendono il caricamento della destinazione desiderata.
Le animazioni di caricamento fluido riducono il carico cognitivo perché gli utenti rimangono nel contesto mentre navigano tra le pagine e riducono la latenza percepita del caricamento perché nel frattempo gli utenti vedono qualcosa di coinvolgente e piacevole. Per questi motivi, la maggior parte delle piattaforme fornisce primitive di facile utilizzo che consentono agli sviluppatori di creare transizioni fluide, ad esempio Android, iOS, MacOS e Windows.
L'API Shared Element Transiziones fornisce agli sviluppatori la stessa funzionalità sul web, indipendentemente dal fatto che le transizioni siano tra documenti (MPA) o intra-documenti (APS).
Per integrare l'API Shared Element Transizione per la parte SPA dell'app di esempio:
- Crea un hook personalizzato per gestire la transizione nel file
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
}
- Chiama l'hook personalizzato
usePageTransitionPrep()
nella pagina dell'elenco, quindi chiama la funzione asincrona per attivare il metodotransition.start()
all'interno dell'eventoclick
.
All'interno della funzione, gli elementi della classe shared-element
vengono raccolti e registrati come elementi condivisi.
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>
)
}
- Nella pagina dei dettagli, chiama l'hook
usePageTransition()
per completare la funzione di callbacktransition.start()
.
In questo callback, vengono registrati anche gli elementi condivisi nella pagina dei dettagli.
pagine/frutta/[nome].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>
)
...
}
Ora puoi vedere che gli elementi dell'immagine sono condivisi nelle pagine dell'elenco e dei dettagli e perfettamente collegati nel passaggio della pagina. Puoi anche personalizzare l'animazione per renderla più creativa con gli pseudo-elementi CSS.
7. Complimenti
Complimenti! Hai creato un'app web immediata e fluida con un'esperienza utente intuitiva, coinvolgente e semplice.
Scopri di più
Prerendering
- Prerendering rinnovato
- Caricamenti istantanei di pagine nel browser attraverso il prerendering speculativo
- link rapido
Bfcache
Precaricamento tra siti
Piattaforme di scambio firmate
Transizioni di elementi condivisi/principali
- Transizioni di elementi condivisi
- Transizioni di pagina semplici e fluide con l'API Shared Element Transizione
Queste API sono ancora nelle prime fasi di sviluppo, quindi condividi il tuo feedback su crbug.com o come problemi nel repository GitHub delle API pertinenti.