Skip to main content

Shallow routing

Lorsque vous naviguez sur une application SvelteKit, vous créez des entrées d’historique. Des clics sur les boutons “Précédent” et “Suivant” vont parcourir cette liste d’entrées, ré-exécutant tout fonction load concernées et remplaçant les composants de page lorsque nécessaire.

Parfois, il est pratique de créer des entrées d’historique sans naviguer. Par exemple, vous pourriez vouloir afficher une modale de dialogue que l’utilisateur ou l’utilisatrice puisse faire disparaître en appuyant sur le bouton “Précédent” du navigateur. Ceci est particulièrement utile sur les appareils mobiles, où utiliser les mouvements des doigts sont souvent plus naturels qu’interagire directement avec l’interface. Dans ces cas-là, une modale non-associée à une entrée d’historique peut être une source de frustration, puisqu’un utilisateur ou utilisatrice pourrait faire glisser son doigt vers la gauche pour tenter de fermer la modale, pour au final se retrouver sur la mauvaise page.

SvelteKit rend cela possible grâce aux fonctions pushState et replaceState, qui vous permettent d’associer un état avec une entrée d’historique sans avoir à naviguer. Par exemple, pour implémenter une modale liée à l’historique :

+page
<script>
	import { pushState } from '$app/navigation';
	import { page } from '$app/state';
	import Modal from './Modal.svelte';

	function showModal() {
		pushState('', {
			showModal: true
		});
	}
</script>

{#if page.state.showModal}
	<Modal close={() => history.back()} />
{/if}
<script lang="ts">
	import { pushState } from '$app/navigation';
	import { page } from '$app/state';
	import Modal from './Modal.svelte';

	function showModal() {
		pushState('', {
			showModal: true
		});
	}
</script>

{#if page.state.showModal}
	<Modal close={() => history.back()} />
{/if}

La modale peut être fermée en naviguant vers l’arrière (en désactivant page.state.showModal) ou en interagissant avec elle de sorte à exécuter le callback close, ce qui déclenchera la navigation arrière programmatiquement.

API

Le premier argument de pushState est l’URL, relative à l’URL courante. Pour rester sur l’URL courante, utilisez ''.

Le deuxième argument est l’état de la nouvelle page, auquel vous pouvez avoir accès via l’objet de page en tant que page.state. Vous pouvez typer l’état de page en déclarant une interface App.PageState (en général dans src/app.d.ts).

Pour définir l’état de page sans créer une nouvelle entrée d’historique, utilisez replaceState plutôt que pushState.

Legacy mode

page.state importé depuis $app/state a été ajouté dans la version 2.12 de SvelteKit. Si vous utilisez une version antérieure ou si vous utilisez Svelte 4, utilisez plutôt $page.state importé depuis $app/stores.

Charger des données pour une route

Lorsque vous utilisez le shallow routing, vous pourriez vouloir afficher une autre +page.svelte à l’intérieur de la page courante. Par exemple, on peut imaginer un clic sur une vignette de photo qui viendrait afficher le détail de la photo sans avoir à naviguer vers la page de la photo.

Pour que cela fonctionne, vous avez besoin de charger les données attendues par +page.svelte. Une manière pratique de faire cela est d’utiliser la fonction preloadData au sein du gestionnaire de click d’un élément <a>. Si l’élément (ou un parent) utilise data-sveltekit-preload-data, les données auront déjà été demandées, et preloadData utilisera alors cette requête.

src/routes/photos/+page
<script>
	import { preloadData, pushState, goto } from '$app/navigation';
	import { page } from '$app/state';
	import Modal from './Modal.svelte';
	import PhotoPage from './[id]/+page.svelte';

	let { data } = $props();
</script>

{#each data.thumbnails as thumbnail}
	<a
		href="/photos/{thumbnail.id}"
		onclick={async (e) => {
			if (innerWidth < 640        // ignore si l'écran est trop petit
				|| e.shiftKey             // ou si le lien est ouvert dans une autre fenêtre
				|| e.metaKey || e.ctrlKey // ou un nouvel onglet (mac: metaKey, win/linux: ctrlKey)
				// on devrait en théorie également considérer un clic avec le bouton de défilement de la
				// souris
			) return;

			// empêche la navigation
			e.preventDefault();

			const { href } = e.currentTarget;

			// ré-exécute les fonctions `load` (ou plutôt, récupère le résultat des fonctions `load` qui
			// sont déjà en cours à cause de `data-sveltekit-preload-data`)
			const result = await preloadData(href);

			if (result.type === 'loaded' && result.status === 200) {
				pushState(href, { selected: result.data });
			} else {
				// quelque chose s'est mal passé ! on essaie de naviguer
				goto(href);
			}
		}}
	>
		<img alt={thumbnail.alt} src={thumbnail.src} />
	</a>
{/each}

{#if page.state.selected}
	<Modal onclose={() => history.back()}>
		<!-- fournit les données de page au composant +page.svelte
				 comme le ferait SvelteKit lors d'une navigation -->
		<PhotoPage data={page.state.selected} />
	</Modal>
{/if}
<script lang="ts">
	import { preloadData, pushState, goto } from '$app/navigation';
	import { page } from '$app/state';
	import Modal from './Modal.svelte';
	import PhotoPage from './[id]/+page.svelte';

	let { data } = $props();
</script>

{#each data.thumbnails as thumbnail}
	<a
		href="/photos/{thumbnail.id}"
		onclick={async (e) => {
			if (innerWidth < 640        // ignore si l'écran est trop petit
				|| e.shiftKey             // ou si le lien est ouvert dans une autre fenêtre
				|| e.metaKey || e.ctrlKey // ou un nouvel onglet (mac: metaKey, win/linux: ctrlKey)
				// on devrait en théorie également considérer un clic avec le bouton de défilement de la
				// souris
			) return;

			// empêche la navigation
			e.preventDefault();

			const { href } = e.currentTarget;

			// ré-exécute les fonctions `load` (ou plutôt, récupère le résultat des fonctions `load` qui
			// sont déjà en cours à cause de `data-sveltekit-preload-data`)
			const result = await preloadData(href);

			if (result.type === 'loaded' && result.status === 200) {
				pushState(href, { selected: result.data });
			} else {
				// quelque chose s'est mal passé ! on essaie de naviguer
				goto(href);
			}
		}}
	>
		<img alt={thumbnail.alt} src={thumbnail.src} />
	</a>
{/each}

{#if page.state.selected}
	<Modal onclose={() => history.back()}>
		<!-- fournit les données de page au composant +page.svelte
				 comme le ferait SvelteKit lors d'une navigation -->
		<PhotoPage data={page.state.selected} />
	</Modal>
{/if}

Inconvénients

Lors du rendu côté serveur,page.state est toujours un objet vide. C’est également le cas pour la première page sur laquelle l’utilisateur ou l’utilisatrice arrive — si cette personne recharge la page (ou revient depuis un autre site), l’état ne sera pas appliqué avant la navigation suivante. During server-side rendering, page.state is always an empty object. The same is true for the fir

Le shallow routing est une fonctionnalité qui nécessite JavaScript pour fonctionner. Soyez-en conscient•e lorsque vous vous en servez et essayez de penser à un comportement de remplacement dans les cas où JavaScript ne serait pas disponible.

Modifier cette page sur Github llms.txt

précédent suivant