## Introduction et objectifs
Dans cet article de blog, j'aimerais passer en revue les fonctionnalités Next.js les plus importantes dont vous aurez besoin dans des scénarios pratiques.
J'ai créé cet article de blog comme référence unique pour moi-même et pour le lecteur intéressé. Au lieu d'avoir à parcourir toute la documentation nextjs. Je pense qu'il sera plus facile d'avoir un article de blog condensé avec toutes les fonctionnalités pratiques importantes suivantes que vous pourrez visiter périodiquement pour rafraîchir vos connaissances !
Nous passerons en revue ensemble les fonctionnalités ci-dessous tout en créant une application de notes en parallèle.
Routeur d'application
Chargement et gestion des erreurs
Actions du serveur
Récupération et mise en cache des données
Streaming et Suspense
Itinéraires parallèles
Gestion des erreurs
Nos notes finales prenant le code d'application ressembleront à ceci :
- app/ - notes/ --------------------------------> Server Side Caching Features - components/ - NotesList.tsx - [noteId]/ - actions/ -------------------------> Server Actions feature - delete-note.action.ts - edit-note.action.ts - components/ - DeleteButton.tsx - page.tsx - edit/ - components/ - EditNoteForm.tsx - page.tsx - loading.tsx --------------------> Page level Streaming feature - create/ - actions/ - create-note.action.ts - components/ - CreateNoteForm.tsx - page.tsx - error-page/ - page.tsx - error.tsx --------------------------> Error Boundary as a page feature - dashboard/ ---------------------------> Component Level Streaming Feature - components/ - NoteActivity.tsx - TagCloud.tsx - NotesSummary.tsx - page.tsx - profile/ ----------------------------->[6] Parallel Routes Feature - layout.tsx - page.tsx - @info/ - page.tsx - loading.tsx - @notes/ - page.tsx - loading.tsx - core/ --------------------------> Our business logic lives here - entities/ - note.ts - use-cases/ - create-note.use-case.ts - update-note.use-case.ts - delete-note.use-case.ts - get-note.use-case.ts - get-notes.use-case.ts - get-notes-summary.use-case.ts - get-recent-activity.use-case.ts - get-recent-tags.use-case.ts
N'hésitez pas à passer directement au code final que vous pouvez trouver dans ce spithacode du référentiel Github.
Alors sans plus attendre, commençons !
Avant de plonger dans le développement de notre application Notes, j'aimerais présenter quelques concepts clés de Nextjs qu'il est important de connaître avant d'aller de l'avant.
Le App Router est un nouveau répertoire "/app" qui prend en charge de nombreuses choses qui n'étaient pas possibles dans l'ancien répertoire "/page" telles que :
Routage imbriqué : vous pouvez imbriquer des dossiers les uns dans les autres. L’URL du chemin de la page suivra la même imbrication de dossier. Par exemple, l'url correspondante de cette page imbriquée /app/notes/[noteId]/edit/page.tsx après avoir supposé que le paramètre dynamique [noteId] est égal à "1" est "/notes/1/edit.
/loading.tsx fichier qui exporte un composant qui est rendu lorsqu'une page est diffusée vers le navigateur de l'utilisateur.
/error.tsx fichier qui exporte un composant qui est rendu lorsqu'une page génère une erreur non détectée.
Routage parallèle et de nombreuses fonctionnalités que nous allons parcourir lors de la création de notre application de notes.
Plongeons dans un sujet vraiment important que tout le monde devrait maîtriser avant même de toucher Nextjs /app Router.
Un composant serveur est essentiellement un composant qui est rendu sur le serveur.
Tout composant qui n'est pas précédé de la directive "use client" est par défaut un composant serveur incluant les pages et les mises en page.
Les composants du serveur peuvent interagir avec n'importe quelle API nodejs ou n'importe quel composant destiné à être utilisé sur le serveur.
Il est possible de faire précéder les composants serveur du mot-clé async contrairement aux composants clients. Vous pouvez donc appeler n'importe quelle fonction asynchrone et l'attendre avant de restituer le composant.
- app/ - notes/ --------------------------------> Server Side Caching Features - components/ - NotesList.tsx - [noteId]/ - actions/ -------------------------> Server Actions feature - delete-note.action.ts - edit-note.action.ts - components/ - DeleteButton.tsx - page.tsx - edit/ - components/ - EditNoteForm.tsx - page.tsx - loading.tsx --------------------> Page level Streaming feature - create/ - actions/ - create-note.action.ts - components/ - CreateNoteForm.tsx - page.tsx - error-page/ - page.tsx - error.tsx --------------------------> Error Boundary as a page feature - dashboard/ ---------------------------> Component Level Streaming Feature - components/ - NoteActivity.tsx - TagCloud.tsx - NotesSummary.tsx - page.tsx - profile/ ----------------------------->[6] Parallel Routes Feature - layout.tsx - page.tsx - @info/ - page.tsx - loading.tsx - @notes/ - page.tsx - loading.tsx - core/ --------------------------> Our business logic lives here - entities/ - note.ts - use-cases/ - create-note.use-case.ts - update-note.use-case.ts - delete-note.use-case.ts - get-note.use-case.ts - get-notes.use-case.ts - get-notes-summary.use-case.ts - get-recent-activity.use-case.ts - get-recent-tags.use-case.ts
Vous vous demandez peut-être pourquoi pré-afficher les composants sur le serveur ?
La réponse peut être résumée en quelques mots SEO, performances et expérience utilisateur.
Lorsque l'utilisateur visite une page, le navigateur télécharge les éléments du site Web, notamment le HTML, le CSS et le javascript.
Le bundle javascript (qui inclut votre code framework) prend plus de temps à charger que le reste des ressources en raison de sa taille.
L'utilisateur devra donc attendre de voir quelque chose à l'écran.
La même chose s'applique pour les crawlers qui sont chargés de l'indexation de votre site Web.
De nombreuses autres métriques SEO telles que le LCP, le TTFB, le Taux de rebond,... seront affectées.
Un composant client est simplement un composant qui est livré au navigateur de l'utilisateur.
Les composants clients ne sont pas de simples composants HTML et CSS. Ils ont besoin d'interactivité pour fonctionner donc il n'est pas vraiment possible de les rendre sur le serveur.
L'interactivité est assurée soit par un framework javascript comme React (useState, useEffect), soit par un navigateur uniquement, soit par des API DOM.
Une déclaration de composant client doit être précédée de la directive "use client". Ce qui indique à Nextjs d'en ignorer la partie interactive (useState,useEffect...) et de l'envoyer directement au navigateur de l'utilisateur.
/client-component.tsx
- app/ - notes/ --------------------------------> Server Side Caching Features - components/ - NotesList.tsx - [noteId]/ - actions/ -------------------------> Server Actions feature - delete-note.action.ts - edit-note.action.ts - components/ - DeleteButton.tsx - page.tsx - edit/ - components/ - EditNoteForm.tsx - page.tsx - loading.tsx --------------------> Page level Streaming feature - create/ - actions/ - create-note.action.ts - components/ - CreateNoteForm.tsx - page.tsx - error-page/ - page.tsx - error.tsx --------------------------> Error Boundary as a page feature - dashboard/ ---------------------------> Component Level Streaming Feature - components/ - NoteActivity.tsx - TagCloud.tsx - NotesSummary.tsx - page.tsx - profile/ ----------------------------->[6] Parallel Routes Feature - layout.tsx - page.tsx - @info/ - page.tsx - loading.tsx - @notes/ - page.tsx - loading.tsx - core/ --------------------------> Our business logic lives here - entities/ - note.ts - use-cases/ - create-note.use-case.ts - update-note.use-case.ts - delete-note.use-case.ts - get-note.use-case.ts - get-notes.use-case.ts - get-notes-summary.use-case.ts - get-recent-activity.use-case.ts - get-recent-tags.use-case.ts
Je sais, les choses les plus frustrantes dans Nextjs, ce sont ces bugs étranges que vous pouvez rencontrer si vous avez manqué les règles d'imbrication entre les Composants serveur et les Composants client.
Donc, dans la section suivante, nous clarifierons cela en présentant les différentes permutations d'imbrication possibles entre les Composants serveur et les Composants client.
Nous allons ignorer ces deux permutations car elles sont évidemment autorisées : Composant Client dans un autre Composant Client et Composant Serveur à l'intérieur d'un autre Composant Serveur.
Vous pouvez importer des Composants client et les restituer normalement dans le composant serveur. Cette permutation est assez évidente car les pages et les mises en page sont par défaut des composants du serveur.
export const ServerComponent = async ()=>{ const posts = await getSomeData() // call any nodejs api or server function during the component rendering // Don't even think about it. No useEffects are allowed here x_x const pasta = await getPasta() return ( <ul> { data.map(d=>( <li>{d.title}</li> )) } </ul> ) }
Imaginez envoyer un composant client au navigateur de l'utilisateur, puis attendre que le composant serveur qui se trouve à l'intérieur de celui-ci restitue et récupère les données. Ce n'est pas possible car le composant serveur est déjà envoyé au client, comment pouvez-vous ensuite le restituer sur le serveur ?
C'est pourquoi ce type de permutation n'est pas pris en charge par Nextjs.
N'oubliez donc jamais d'éviter d'importer des Composants serveur à l'intérieur des Composants client pour les afficher en tant qu'enfants.
"use client" import React,{useEffect,useState} from "react" export const ClientComponent = ()=>{ const [value,setValue] = useState() useEffect(()=>{ alert("Component have mounted!") return ()=>{ alert("Component is unmounted") } },[]) //.......... return ( <> <button onClick={()=>alert("Hello, from browser")}></button> {/* .......... JSX Code ...............*/} </> ) }
Essayez toujours de réduire le javascript envoyé au navigateur de l'utilisateur en poussant les composants clients vers le bas dans l'arborescence jsx.
Il n'est pas possible d'importer et de restituer directement un Composant serveur en tant qu'enfant d'un Composant client mais il existe une solution de contournement qui utilise la nature de composabilité de réaction.
L'astuce consiste à transmettre le Composant serveur en tant qu'enfant du Composant client à un composant serveur de niveau supérieur ( ParentServerComponent).
Appelons ça le Trick de Papa :D.
Cette astuce garantit que le Composant serveur transmis est rendu sur le serveur avant d'envoyer le Composant client au navigateur de l'utilisateur.
import { ClientComponent } from '@/components' // Allowed :) export const ServerComponent = ()=>{ return ( <> <ClientComponent/> </> ) }
Nous verrons un exemple concret sur la /app/page.tsx page d'accueil de notre application notes.
Où nous allons rendre un composant serveur passé en tant qu'enfant à l'intérieur d'un composant client. Le composant client peut afficher ou masquer de manière conditionnelle le contenu rendu du composant serveur en fonction d'une valeur de variable d'état booléenne.
Actions du serveur est une fonctionnalité nextjs intéressante qui permet d'appeler à distance et en toute sécurité une fonction qui est déclarée sur le serveur à partir de vos composants côté client .
Pour déclarer une action serveur il vous suffit d'ajouter la directive "use server" dans le corps de la fonction comme indiqué ci-dessous.
- app/ - notes/ --------------------------------> Server Side Caching Features - components/ - NotesList.tsx - [noteId]/ - actions/ -------------------------> Server Actions feature - delete-note.action.ts - edit-note.action.ts - components/ - DeleteButton.tsx - page.tsx - edit/ - components/ - EditNoteForm.tsx - page.tsx - loading.tsx --------------------> Page level Streaming feature - create/ - actions/ - create-note.action.ts - components/ - CreateNoteForm.tsx - page.tsx - error-page/ - page.tsx - error.tsx --------------------------> Error Boundary as a page feature - dashboard/ ---------------------------> Component Level Streaming Feature - components/ - NoteActivity.tsx - TagCloud.tsx - NotesSummary.tsx - page.tsx - profile/ ----------------------------->[6] Parallel Routes Feature - layout.tsx - page.tsx - @info/ - page.tsx - loading.tsx - @notes/ - page.tsx - loading.tsx - core/ --------------------------> Our business logic lives here - entities/ - note.ts - use-cases/ - create-note.use-case.ts - update-note.use-case.ts - delete-note.use-case.ts - get-note.use-case.ts - get-notes.use-case.ts - get-notes-summary.use-case.ts - get-recent-activity.use-case.ts - get-recent-tags.use-case.ts
La directive "use server" indique à Nextjs que la fonction contient du code côté serveur qui s'exécute uniquement sur le serveur.
Sous le capot, Nextjs envoie l'ID d'action et crée un point de terminaison réservé pour cette action.
Ainsi, lorsque vous appelez cette action dans un composant client Nextjs effectuera une requête POST au point de terminaison unique de l'action identifié par l'Action Id tout en transmettant le arguments sérialisés que vous avez transmis lors de l'appel de l'action dans le corps de la requête.
Mieux vaut clarifier cela avec cet exemple simplifié.
Nous avons vu précédemment, qu'il fallait utiliser le "use server" dans la directive du corps de la fonction pour déclarer une action du serveur. Mais que se passe-t-il si vous devez déclarer plusieurs actions du serveur à la fois.
Eh bien, vous pouvez simplement utiliser la directive en-tête ou au début d'un fichier comme indiqué dans le code ci-dessous.
/server/actions.ts
export const ServerComponent = async ()=>{ const posts = await getSomeData() // call any nodejs api or server function during the component rendering // Don't even think about it. No useEffects are allowed here x_x const pasta = await getPasta() return ( <ul> { data.map(d=>( <li>{d.title}</li> )) } </ul> ) }
Notez que l'action du serveur doit toujours être marquée comme asynchrone
Donc, dans le code ci-dessus, nous avons déclaré une action serveur nommée createLogAction.
L'action est chargée de sauvegarder une entrée de journal dans un fichier spécifique sur le serveur sous le répertoire /logs.
Le fichier est nommé en fonction de l'argument d'action name.
L'action Ajoute une entrée de journal qui comprend la date de création et l'argument de l'action message.
Maintenant, utilisons notre action créée dans le composant côté client CreateLogButton.
/components/CreateLogButton.tsx
"use client" import React,{useEffect,useState} from "react" export const ClientComponent = ()=>{ const [value,setValue] = useState() useEffect(()=>{ alert("Component have mounted!") return ()=>{ alert("Component is unmounted") } },[]) //.......... return ( <> <button onClick={()=>alert("Hello, from browser")}></button> {/* .......... JSX Code ...............*/} </> ) }
Le composant bouton déclare une variable d'état locale nommée isSubmitting qui est utilisée pour savoir si l'action est en cours d'exécution ou non. Lorsque l'action est en cours d'exécution, le texte du bouton passe de "Bouton de journalisation" à "Chargement...".
L'action serveur est appelée lorsque l'on clique sur le composant Log Button.
Tout d'abord, commençons par créer nos schémas et types de validation de notes.
Comme les modèles sont censés gérer la validation des données, nous utiliserons à cet effet une bibliothèque populaire appelée zod.
Ce qui est cool avec zod, c'est son API descriptive facile à comprendre qui fait de la définition du modèle et de la génération du TypeScript correspondant une tâche transparente.
Nous n'utiliserons pas de modèle complexe et sophistiqué pour nos notes. Chaque note aura un identifiant unique, un titre, un contenu et un champ de date de création.
- app/ - notes/ --------------------------------> Server Side Caching Features - components/ - NotesList.tsx - [noteId]/ - actions/ -------------------------> Server Actions feature - delete-note.action.ts - edit-note.action.ts - components/ - DeleteButton.tsx - page.tsx - edit/ - components/ - EditNoteForm.tsx - page.tsx - loading.tsx --------------------> Page level Streaming feature - create/ - actions/ - create-note.action.ts - components/ - CreateNoteForm.tsx - page.tsx - error-page/ - page.tsx - error.tsx --------------------------> Error Boundary as a page feature - dashboard/ ---------------------------> Component Level Streaming Feature - components/ - NoteActivity.tsx - TagCloud.tsx - NotesSummary.tsx - page.tsx - profile/ ----------------------------->[6] Parallel Routes Feature - layout.tsx - page.tsx - @info/ - page.tsx - loading.tsx - @notes/ - page.tsx - loading.tsx - core/ --------------------------> Our business logic lives here - entities/ - note.ts - use-cases/ - create-note.use-case.ts - update-note.use-case.ts - delete-note.use-case.ts - get-note.use-case.ts - get-notes.use-case.ts - get-notes-summary.use-case.ts - get-recent-activity.use-case.ts - get-recent-tags.use-case.ts
Nous déclarons également quelques schémas supplémentaires utiles comme InsertNoteSchema et WhereNoteSchema qui nous faciliteront la vie lorsque nous créerons nos fonctions réutilisables qui manipuleront notre modèle plus tard.
Nous stockerons et manipulerons nos notes en mémoire.
export const ServerComponent = async ()=>{ const posts = await getSomeData() // call any nodejs api or server function during the component rendering // Don't even think about it. No useEffects are allowed here x_x const pasta = await getPasta() return ( <ul> { data.map(d=>( <li>{d.title}</li> )) } </ul> ) }
Nous stockons notre tableau notes dans l'objet global this pour éviter de perdre l'état de notre tableau à chaque fois que la constante notes est importée dans un fichier (rechargement de page...).
Le cas d'utilisation createNote nous permettra d'insérer une note dans le tableau notes. Considérez la méthode notes.unshift comme l'inverse de la méthode notes.push car elle pousse l'élément au début du tableau au lieu de la fin de celui-ci.
"use client" import React,{useEffect,useState} from "react" export const ClientComponent = ()=>{ const [value,setValue] = useState() useEffect(()=>{ alert("Component have mounted!") return ()=>{ alert("Component is unmounted") } },[]) //.......... return ( <> <button onClick={()=>alert("Hello, from browser")}></button> {/* .......... JSX Code ...............*/} </> ) }
Nous utiliserons updateNote pour mettre à jour une note spécifique dans le tableau notes en fonction de son identifiant. Il trouve d'abord l'index des éléments, renvoie une erreur s'il n'est pas trouvé et renvoie la note correspondante en fonction de l'index trouvé.
import { ClientComponent } from '@/components' // Allowed :) export const ServerComponent = ()=>{ return ( <> <ClientComponent/> </> ) }
La fonction de cas d'utilisation deleteNote sera utilisée pour supprimer une note donnée en fonction de l'identifiant de la note.
La méthode fonctionne de la même manière, elle trouve d'abord l'index de la note en fonction de son identifiant, renvoie une erreur si elle n'est pas trouvée puis renvoie la note correspondante indexée par l'identifiant trouvé.
"use client" import { ServerComponent } from '@/components' // Not allowed :( export const ClientComponent = ()=>{ return ( <> <ServerComponent/> </> ) }
La fonction getNote est explicite, elle trouvera simplement une note compte tenu de son identifiant.
import {ClientComponent} from '@/components/...' import {ServerComponent} from '@/components/...' export const ParentServerComponent = ()=>{ return ( <> <ClientComponent> <ServerComponent/> </ClientComponent> </> ) }
Comme nous ne voulons pas déplacer l'intégralité de notre base de données de notes côté client, nous ne récupérerons qu'une partie du total des notes disponibles. Nous devons donc implémenter une pagination côté serveur.
export const Component = ()=>{ const serverActionFunction = async(params:any)=>{ "use server" // server code lives here //... / } const handleClick = ()=>{ await serverActionFunction() } return <button onClick={handleClick}>click me</button> }
Donc, la fonction getNotes nous permettra essentiellement de récupérer une page spécifique de notre serveur en passant l'argument page.
L'argument limit sert à déterminer le nombre d'éléments qui sont présents sur une page donnée.
Par exemple :
Si le tableau notes contient 100 éléments et que l'argument limit est égal à 10.
En demandant la page 1 à notre serveur seuls les 10 premiers éléments seront retournés.
L'argument search sera utilisé pour implémenter la recherche côté serveur. Il indiquera au serveur de renvoyer uniquement les notes qui ont la chaîne search comme sous-chaîne soit dans le titre, soit dans les attributs de contenu.
- app/ - notes/ --------------------------------> Server Side Caching Features - components/ - NotesList.tsx - [noteId]/ - actions/ -------------------------> Server Actions feature - delete-note.action.ts - edit-note.action.ts - components/ - DeleteButton.tsx - page.tsx - edit/ - components/ - EditNoteForm.tsx - page.tsx - loading.tsx --------------------> Page level Streaming feature - create/ - actions/ - create-note.action.ts - components/ - CreateNoteForm.tsx - page.tsx - error-page/ - page.tsx - error.tsx --------------------------> Error Boundary as a page feature - dashboard/ ---------------------------> Component Level Streaming Feature - components/ - NoteActivity.tsx - TagCloud.tsx - NotesSummary.tsx - page.tsx - profile/ ----------------------------->[6] Parallel Routes Feature - layout.tsx - page.tsx - @info/ - page.tsx - loading.tsx - @notes/ - page.tsx - loading.tsx - core/ --------------------------> Our business logic lives here - entities/ - note.ts - use-cases/ - create-note.use-case.ts - update-note.use-case.ts - delete-note.use-case.ts - get-note.use-case.ts - get-notes.use-case.ts - get-notes-summary.use-case.ts - get-recent-activity.use-case.ts - get-recent-tags.use-case.ts
Ce cas d'utilisation sera utilisé pour obtenir de fausses données sur les activités récentes des utilisateurs.
Nous utiliserons cette fonction dans la page /dashboard.
export const ServerComponent = async ()=>{ const posts = await getSomeData() // call any nodejs api or server function during the component rendering // Don't even think about it. No useEffects are allowed here x_x const pasta = await getPasta() return ( <ul> { data.map(d=>( <li>{d.title}</li> )) } </ul> ) }
Cette fonction de cas d'utilisation sera chargée d'obtenir des statistiques sur les différentes balises utilisées dans nos notes (#something).
Nous utiliserons cette fonction dans la page /dashboard.
"use client" import React,{useEffect,useState} from "react" export const ClientComponent = ()=>{ const [value,setValue] = useState() useEffect(()=>{ alert("Component have mounted!") return ()=>{ alert("Component is unmounted") } },[]) //.......... return ( <> <button onClick={()=>alert("Hello, from browser")}></button> {/* .......... JSX Code ...............*/} </> ) }
Nous utiliserons cette fonction de cas d'utilisation pour simplement renvoyer de fausses données sur certaines informations utilisateur telles que le nom, l'e-mail...
Nous utiliserons cette fonction dans la page /dashboard.
import { ClientComponent } from '@/components' // Allowed :) export const ServerComponent = ()=>{ return ( <> <ClientComponent/> </> ) }
"use client" import { ServerComponent } from '@/components' // Not allowed :( export const ClientComponent = ()=>{ return ( <> <ServerComponent/> </> ) }
Dans cette page d'accueil, nous présenterons l'astuce ou la solution de contournement précédente pour rendre un Composant serveur à l'intérieur d'un Composant client (L'astuce PaPa :D) .
/app/page.tsx
import {ClientComponent} from '@/components/...' import {ServerComponent} from '@/components/...' export const ParentServerComponent = ()=>{ return ( <> <ClientComponent> <ServerComponent/> </ClientComponent> </> ) }
Dans le code ci-dessus, nous déclarons un Composant serveur parent appelé Accueil qui est responsable du rendu de la page "/" dans notre application.
Nous importons un Composant Serveur nommé RandomNote et un ClientComponent nommé NoteOfTheDay.
Nous transmettons le composant serveur RandomNote en tant qu'enfant au composant côté client NoteOfTheDay.
/app/components/RandomNote.ts
export const Component = ()=>{ const serverActionFunction = async(params:any)=>{ "use server" // server code lives here //... / } const handleClick = ()=>{ await serverActionFunction() } return <button onClick={handleClick}>click me</button> }
Le composant serveur RandomNote fonctionne comme suit :
il récupère une note aléatoire à l'aide de la fonction de cas d'utilisation getRandomNote.
il restitue les détails de la note qui se composent du titre et d'une partie ou sous-chaîne de la note complète contenu.
/app/components/NoteOfTheDay.ts
- app/ - notes/ --------------------------------> Server Side Caching Features - components/ - NotesList.tsx - [noteId]/ - actions/ -------------------------> Server Actions feature - delete-note.action.ts - edit-note.action.ts - components/ - DeleteButton.tsx - page.tsx - edit/ - components/ - EditNoteForm.tsx - page.tsx - loading.tsx --------------------> Page level Streaming feature - create/ - actions/ - create-note.action.ts - components/ - CreateNoteForm.tsx - page.tsx - error-page/ - page.tsx - error.tsx --------------------------> Error Boundary as a page feature - dashboard/ ---------------------------> Component Level Streaming Feature - components/ - NoteActivity.tsx - TagCloud.tsx - NotesSummary.tsx - page.tsx - profile/ ----------------------------->[6] Parallel Routes Feature - layout.tsx - page.tsx - @info/ - page.tsx - loading.tsx - @notes/ - page.tsx - loading.tsx - core/ --------------------------> Our business logic lives here - entities/ - note.ts - use-cases/ - create-note.use-case.ts - update-note.use-case.ts - delete-note.use-case.ts - get-note.use-case.ts - get-notes.use-case.ts - get-notes-summary.use-case.ts - get-recent-activity.use-case.ts - get-recent-tags.use-case.ts
Le composant client NoteOfTheDay de l'autre côté fonctionne comme décrit ci-dessous :
/app/notes/page.tsx
export const ServerComponent = async ()=>{ const posts = await getSomeData() // call any nodejs api or server function during the component rendering // Don't even think about it. No useEffects are allowed here x_x const pasta = await getPasta() return ( <ul> { data.map(d=>( <li>{d.title}</li> )) } </ul> ) }
Nous allons commencer par créer la page /app/notes/page.tsx qui est un composant serveur chargé de :
Obtention des paramètres de recherche de page qui sont les chaînes attachées à la fin de l'URL après la marque ? : http://localhost:3000/notes?page=1&search=Something
Passer les paramètres de recherche dans une fonction déclarée localement appelée fetchNotes.
La fonction fetchNotes utilise notre fonction de cas d'utilisation précédemment déclarée getNotes pour récupérer la page de notes actuelle.
Vous pouvez remarquer que nous enveloppons la fonction getNotes avec une fonction utilitaire importée de "next/cache" appelée unstable_cache. La fonction de cache instable est utilisée pour mettre en cache la réponse de la fonction getNotes.
Si nous sommes sûrs qu'aucune note n'est ajoutée à la base de données. Cela n'a aucun sens de cliquer dessus à chaque fois que la page est rechargée. Ainsi, la fonction unstable_cache marque le résultat de la fonction getNotes avec la balise "notes" que nous pourrons utiliser plus tard pour invalider les "notes" mettre en cache si une note est ajoutée ou supprimée.
La fonction fetchNotes renvoie deux valeurs : les notes et le total.
Les données résultantes (notes et total) sont transmises à un Composant côté client appelé NotesList qui est responsable du rendu de nos notes.
Lorsque l'utilisateur clique sur Actualiser. Une page vierge apparaîtra à l'utilisateur pendant la récupération de nos données de notes.
Pour résoudre ce problème, nous utiliserons une fonctionnalité géniale de Nextjs appelée. Diffusion de pages côté serveur.
Nous pouvons le faire en créant un fichier loading.tsx, à côté de notre fichier /app/notes/page.tsx.
/app/notes/loading.tsx
"use client" import React,{useEffect,useState} from "react" export const ClientComponent = ()=>{ const [value,setValue] = useState() useEffect(()=>{ alert("Component have mounted!") return ()=>{ alert("Component is unmounted") } },[]) //.......... return ( <> <button onClick={()=>alert("Hello, from browser")}></button> {/* .......... JSX Code ...............*/} </> ) }
Pendant que la page est diffusée depuis le serveur, l'utilisateur verra une page de chargement squelette, qui donne à l'utilisateur une idée du type de contenu à venir.
N'est-ce pas cool :). créez simplement un fichier chargement.tsx et voilà, vous avez terminé. Votre ux s'épanouit jusqu'au niveau supérieur.
/app/notes/components/NotesList.tsx
- app/ - notes/ --------------------------------> Server Side Caching Features - components/ - NotesList.tsx - [noteId]/ - actions/ -------------------------> Server Actions feature - delete-note.action.ts - edit-note.action.ts - components/ - DeleteButton.tsx - page.tsx - edit/ - components/ - EditNoteForm.tsx - page.tsx - loading.tsx --------------------> Page level Streaming feature - create/ - actions/ - create-note.action.ts - components/ - CreateNoteForm.tsx - page.tsx - error-page/ - page.tsx - error.tsx --------------------------> Error Boundary as a page feature - dashboard/ ---------------------------> Component Level Streaming Feature - components/ - NoteActivity.tsx - TagCloud.tsx - NotesSummary.tsx - page.tsx - profile/ ----------------------------->[6] Parallel Routes Feature - layout.tsx - page.tsx - @info/ - page.tsx - loading.tsx - @notes/ - page.tsx - loading.tsx - core/ --------------------------> Our business logic lives here - entities/ - note.ts - use-cases/ - create-note.use-case.ts - update-note.use-case.ts - delete-note.use-case.ts - get-note.use-case.ts - get-notes.use-case.ts - get-notes-summary.use-case.ts - get-recent-activity.use-case.ts - get-recent-tags.use-case.ts
La liste de notes Composant côté client Reçoit les notes et les données liées à la pagination de son Composant serveur parent qui est la Page Notes.
Ensuite, le composant gère le rendu de la page de notes actuelle. Chaque carte de note individuelle est rendue à l'aide du composant NoteView.
Il fournit également des liens vers la page précédente et suivante à l'aide du composant Next.js Link qui est essentiel pour pré-récupérer les données de la page suivante et précédente afin de nous permettre d'avoir un client transparent et rapide -navigation latérale.
Pour gérer la Recherche côté serveur, nous utilisons un hook personnalisé appelé useNotesSearch qui gère essentiellement le déclenchement d'une récupération de notes lorsqu'un utilisateur tape une requête spécifique dans la recherche Input.
/app/notes/components/NoteView.ts
export const ServerComponent = async ()=>{ const posts = await getSomeData() // call any nodejs api or server function during the component rendering // Don't even think about it. No useEffects are allowed here x_x const pasta = await getPasta() return ( <ul> { data.map(d=>( <li>{d.title}</li> )) } </ul> ) }
Le composant NoteView est simple, il est uniquement responsable du rendu de chaque carte de note individuelle avec son correspondant : le titre, une partie du contenu et des liens d'action pour afficher les détails de la note ou pour la modifier.
/app/notes/components/hooks/use-notes-search.ts
"use client" import React,{useEffect,useState} from "react" export const ClientComponent = ()=>{ const [value,setValue] = useState() useEffect(()=>{ alert("Component have mounted!") return ()=>{ alert("Component is unmounted") } },[]) //.......... return ( <> <button onClick={()=>alert("Hello, from browser")}></button> {/* .......... JSX Code ...............*/} </> ) }
Le hook personnalisé useNotesSearch fonctionne comme suit :
Il stocke le prop initialSearch dans un état local à l'aide du hook useState.
Nous utilisons le hook useEffect React pour déclencher une navigation de page chaque fois que les valeurs des variables currentPage ou debouncingSearchValue changent.
La nouvelle URL de la page est construite en tenant compte de la page actuelle et des valeurs de recherche.
La fonction setSearch sera appelée chaque fois qu'un caractère change lorsque l'utilisateur tape quelque chose dans l'entrée de recherche. Cela entraînera trop de navigations en peu de temps.
Pour éviter que nous déclenchions la navigation uniquement lorsque l'utilisateur arrête de taper d'autres termes, nous faisons rebondir la valeur de recherche pendant une durée spécifique (300 ms dans notre cas).
Ensuite, passons en revue le /app/notes/create/page.tsx qui est un wrapper de composant serveur autour du composant client CreateNoteForm.
/app/notes/create/page.tsx
import { ClientComponent } from '@/components' // Allowed :) export const ServerComponent = ()=>{ return ( <> <ClientComponent/> </> ) }
/app/notes/create/components/CreateNoteForm.tsx
- app/ - notes/ --------------------------------> Server Side Caching Features - components/ - NotesList.tsx - [noteId]/ - actions/ -------------------------> Server Actions feature - delete-note.action.ts - edit-note.action.ts - components/ - DeleteButton.tsx - page.tsx - edit/ - components/ - EditNoteForm.tsx - page.tsx - loading.tsx --------------------> Page level Streaming feature - create/ - actions/ - create-note.action.ts - components/ - CreateNoteForm.tsx - page.tsx - error-page/ - page.tsx - error.tsx --------------------------> Error Boundary as a page feature - dashboard/ ---------------------------> Component Level Streaming Feature - components/ - NoteActivity.tsx - TagCloud.tsx - NotesSummary.tsx - page.tsx - profile/ ----------------------------->[6] Parallel Routes Feature - layout.tsx - page.tsx - @info/ - page.tsx - loading.tsx - @notes/ - page.tsx - loading.tsx - core/ --------------------------> Our business logic lives here - entities/ - note.ts - use-cases/ - create-note.use-case.ts - update-note.use-case.ts - delete-note.use-case.ts - get-note.use-case.ts - get-notes.use-case.ts - get-notes-summary.use-case.ts - get-recent-activity.use-case.ts - get-recent-tags.use-case.ts
Le formulaire du composant client CreateNoteForm se charge de récupérer les données de l'utilisateur puis de les stocker dans des variables d'état locales (titre, contenu).
Lorsque le formulaire est soumis après avoir cliqué sur le bouton de soumission, le createNoteAction est soumis avec le titre et le contenu arguments d'état local. .
La variable booléenne d'état isSubmitting est utilisée pour suivre l'état de soumission de l'action.
Si le createNoteAction est soumis avec succès sans aucune erreur, nous redirigeons l'utilisateur vers la page /notes.
/app/notes/create/actions/create-note.action.tsx
export const ServerComponent = async ()=>{ const posts = await getSomeData() // call any nodejs api or server function during the component rendering // Don't even think about it. No useEffects are allowed here x_x const pasta = await getPasta() return ( <ul> { data.map(d=>( <li>{d.title}</li> )) } </ul> ) }
Le code de l'action createNoteAction est simple, le fichier contenant est précédé de la directive "use server" indiquant à Next.js que cette action est appelable dans les composants clients.
Un point que nous devons souligner à propos des actions du serveur est que seule l'interface de l'action est envoyée au client, mais pas le code contenu dans l'action elle-même.
En d'autres termes, le code à l'intérieur de l'action vivra sur le serveur, nous ne devons donc faire confiance à aucune entrée provenant du client vers notre serveur.
C'est pourquoi nous utilisons zod ici pour valider l'argument d'action rawNote en utilisant notre schéma créé précédemment.
Après avoir validé nos entrées, nous appelons le cas d'utilisation createNote avec les données validées.
Si la note est créée avec succès, la fonction revalidateTag est appelée pour invalider l'entrée de cache qui est étiquetée comme "notes" (rappelez-vous la fonction unstable_cache qui est utilisé dans la page /notes).
La page de détails des notes affiche le titre et le contenu complet d'une note spécifique en fonction de son identifiant unique. En plus de cela, il affiche quelques boutons d'action pour modifier ou supprimer la note.
/app/notes/[noteId]/page.tsx
"use client" import React,{useEffect,useState} from "react" export const ClientComponent = ()=>{ const [value,setValue] = useState() useEffect(()=>{ alert("Component have mounted!") return ()=>{ alert("Component is unmounted") } },[]) //.......... return ( <> <button onClick={()=>alert("Hello, from browser")}></button> {/* .......... JSX Code ...............*/} </> ) }
Nous récupérons d’abord les paramètres de la page à partir des accessoires de la page. Dans Next.js 13, nous devons attendre l'argument de la page params car c'est une promesse.
Après cela, nous transmettons le params.noteId à la fonction fetchNote déclarée localement.
/app/notes/[noteId]/fetchers/fetch-note.ts
- app/ - notes/ --------------------------------> Server Side Caching Features - components/ - NotesList.tsx - [noteId]/ - actions/ -------------------------> Server Actions feature - delete-note.action.ts - edit-note.action.ts - components/ - DeleteButton.tsx - page.tsx - edit/ - components/ - EditNoteForm.tsx - page.tsx - loading.tsx --------------------> Page level Streaming feature - create/ - actions/ - create-note.action.ts - components/ - CreateNoteForm.tsx - page.tsx - error-page/ - page.tsx - error.tsx --------------------------> Error Boundary as a page feature - dashboard/ ---------------------------> Component Level Streaming Feature - components/ - NoteActivity.tsx - TagCloud.tsx - NotesSummary.tsx - page.tsx - profile/ ----------------------------->[6] Parallel Routes Feature - layout.tsx - page.tsx - @info/ - page.tsx - loading.tsx - @notes/ - page.tsx - loading.tsx - core/ --------------------------> Our business logic lives here - entities/ - note.ts - use-cases/ - create-note.use-case.ts - update-note.use-case.ts - delete-note.use-case.ts - get-note.use-case.ts - get-notes.use-case.ts - get-notes-summary.use-case.ts - get-recent-activity.use-case.ts - get-recent-tags.use-case.ts
La fonction fetchNote enveloppe notre cas d'utilisation getNote avec le unstable_cache tout en marquant le résultat renvoyé avec "note-details" et note-details/${id} Balises.
La balise "note-details" peut être utilisée pour invalider toutes les entrées du cache des détails de la note à la fois.
En revanche, la balise note-details/${id} est associée uniquement à une note spécifique définie par son identifiant unique. Nous pouvons donc l'utiliser pour invalider l'entrée dans le cache d'une note spécifique au lieu de l'ensemble des notes.
/app/notes/[noteId]/loading.tsx
export const ServerComponent = async ()=>{ const posts = await getSomeData() // call any nodejs api or server function during the component rendering // Don't even think about it. No useEffects are allowed here x_x const pasta = await getPasta() return ( <ul> { data.map(d=>( <li>{d.title}</li> )) } </ul> ) }
Rappel
Le loading.tsx est une page Next.js spéciale qui est rendue pendant que la page de détails de la note récupère ses données sur le serveur.
Ou en d'autres termes, pendant que la fonction fetchNote exécute une page squelette sera affichée à l'utilisateur au lieu d'un écran vide.
Cette fonctionnalité nextjs s'appelle Page Streaming. Il permet d'envoyer l'intégralité de la mise en page parent statique d'une page dynamique tout en diffusant progressivement son contenu.
Cela augmente les performances et l'expérience utilisateur en évitant de bloquer l'interface utilisateur pendant que le contenu dynamique d'une page est récupéré sur le serveur.
/app/notes/[noteId]/components/DeleteNoteButton.tsx
"use client" import React,{useEffect,useState} from "react" export const ClientComponent = ()=>{ const [value,setValue] = useState() useEffect(()=>{ alert("Component have mounted!") return ()=>{ alert("Component is unmounted") } },[]) //.......... return ( <> <button onClick={()=>alert("Hello, from browser")}></button> {/* .......... JSX Code ...............*/} </> ) }
Plongeons maintenant dans le composant côté client DeleteNoteButton.
Le composant est responsable du rendu d'un bouton de suppression et de l'exécution de la deleteNoteAction, puis de la redirection de l'utilisateur vers la page /notes lorsque l'action est exécutée avec succès.
Pour suivre l'état d'exécution de l'action, nous utilisons une variable d'état locale isDeleting.
/app/notes/[noteId]/actions/delete-note.action.tsx
import { ClientComponent } from '@/components' // Allowed :) export const ServerComponent = ()=>{ return ( <> <ClientComponent/> </> ) }
Le code deleteNoteAction fonctionne comme suit :
/app/notes/[noteId]/edit/page.tsx
- app/ - notes/ --------------------------------> Server Side Caching Features - components/ - NotesList.tsx - [noteId]/ - actions/ -------------------------> Server Actions feature - delete-note.action.ts - edit-note.action.ts - components/ - DeleteButton.tsx - page.tsx - edit/ - components/ - EditNoteForm.tsx - page.tsx - loading.tsx --------------------> Page level Streaming feature - create/ - actions/ - create-note.action.ts - components/ - CreateNoteForm.tsx - page.tsx - error-page/ - page.tsx - error.tsx --------------------------> Error Boundary as a page feature - dashboard/ ---------------------------> Component Level Streaming Feature - components/ - NoteActivity.tsx - TagCloud.tsx - NotesSummary.tsx - page.tsx - profile/ ----------------------------->[6] Parallel Routes Feature - layout.tsx - page.tsx - @info/ - page.tsx - loading.tsx - @notes/ - page.tsx - loading.tsx - core/ --------------------------> Our business logic lives here - entities/ - note.ts - use-cases/ - create-note.use-case.ts - update-note.use-case.ts - delete-note.use-case.ts - get-note.use-case.ts - get-notes.use-case.ts - get-notes-summary.use-case.ts - get-recent-activity.use-case.ts - get-recent-tags.use-case.ts
La page /app/notes/[noteId]/edit/page.tsx est un composant serveur qui obtient le paramètre noteId de la promesse params.
Ensuite, il récupère la note à l'aide de la fonction fetchNote.
Après une récupération réussie. Il transmet la note au composant côté client EditNoteForm.
/app/notes/[noteId]/edit/components/EditNoteForm.tsx
export const ServerComponent = async ()=>{ const posts = await getSomeData() // call any nodejs api or server function during the component rendering // Don't even think about it. No useEffects are allowed here x_x const pasta = await getPasta() return ( <ul> { data.map(d=>( <li>{d.title}</li> )) } </ul> ) }
Le composant côté client EditNoteForm reçoit la note et affiche un formulaire qui permet à l'utilisateur de mettre à jour les détails de la note.
Les variables d'état locales title et content sont utilisées pour stocker leurs valeurs d'entrée ou de zone de texte correspondantes.
Lorsque le formulaire est soumis via le bouton Mettre à jour la note. Le updateNoteAction est appelé avec les valeurs title et content comme arguments.
La variable d'état isSubmitting est utilisée pour suivre l'état de soumission de l'action, permettant d'afficher un indicateur de chargement lorsque l'action est en cours d'exécution.
/app/notes/[noteId]/edit/actions/edit-note.action.ts
"use client" import React,{useEffect,useState} from "react" export const ClientComponent = ()=>{ const [value,setValue] = useState() useEffect(()=>{ alert("Component have mounted!") return ()=>{ alert("Component is unmounted") } },[]) //.......... return ( <> <button onClick={()=>alert("Hello, from browser")}></button> {/* .......... JSX Code ...............*/} </> ) }
L'action updateNoteAction fonctionne comme suit :
/app/dashboard/page.tsx
import { ClientComponent } from '@/components' // Allowed :) export const ServerComponent = ()=>{ return ( <> <ClientComponent/> </> ) }
La page /app/dashboard/page.tsx est divisée en composants côté serveur plus petits : NotesSummary, RecentActivity et TagCloud.
Chaque composant du serveur récupère ses propres données indépendamment.
Chaque composant du serveur est enveloppé dans une limite React Suspense.
Le rôle de la limite de suspense est d'afficher un composant de secours (un Squelette dans notre cas) lorsque le composant serveur enfant récupère ses propres données.
Ou en d'autres termes, la limite Suspense nous permet de différer ou de retarder le rendu de ses enfants jusqu'à ce qu'une condition soit remplie (les données à l'intérieur des enfants sont en cours de chargement).
Ainsi, l'utilisateur pourra voir la page comme une combinaison d'un tas de squelettes. Pendant que la réponse pour chaque composant individuel est diffusée par le serveur.
L'un des principaux avantages de cette approche est d'éviter de bloquer l'interface utilisateur si un ou plusieurs composants du serveur prennent plus de temps que l'autre.
Donc, si nous supposons que les temps de récupération individuels pour chaque composant sont répartis comme suit :
Lorsque nous appuyons sur Actualiser, la première chose que nous verrons, ce sont 3 chargeurs squelettes.
Après 1 seconde, le composant recentActivity apparaîtra.
Après 2 secondes le NotesSummary suivra puis le TagCloud.
Donc, au lieu de faire attendre l'utilisateur 3 secondes avant de voir un contenu. Nous avons réduit ce temps de 2 secondes en affichant en premier la RecentActivity.
Cette approche de rendu incrémentiel se traduit par une meilleure expérience utilisateur et de meilleures performances.
Le code des composants du serveur individuels est mis en évidence ci-dessous.
/app/dashboard/components/RecentActivity.tsx
- app/ - notes/ --------------------------------> Server Side Caching Features - components/ - NotesList.tsx - [noteId]/ - actions/ -------------------------> Server Actions feature - delete-note.action.ts - edit-note.action.ts - components/ - DeleteButton.tsx - page.tsx - edit/ - components/ - EditNoteForm.tsx - page.tsx - loading.tsx --------------------> Page level Streaming feature - create/ - actions/ - create-note.action.ts - components/ - CreateNoteForm.tsx - page.tsx - error-page/ - page.tsx - error.tsx --------------------------> Error Boundary as a page feature - dashboard/ ---------------------------> Component Level Streaming Feature - components/ - NoteActivity.tsx - TagCloud.tsx - NotesSummary.tsx - page.tsx - profile/ ----------------------------->[6] Parallel Routes Feature - layout.tsx - page.tsx - @info/ - page.tsx - loading.tsx - @notes/ - page.tsx - loading.tsx - core/ --------------------------> Our business logic lives here - entities/ - note.ts - use-cases/ - create-note.use-case.ts - update-note.use-case.ts - delete-note.use-case.ts - get-note.use-case.ts - get-notes.use-case.ts - get-notes-summary.use-case.ts - get-recent-activity.use-case.ts - get-recent-tags.use-case.ts
Le composant serveur RecentActivity récupère essentiellement les dernières activités à l'aide de la fonction de cas d'utilisation getRecentActivity et les restitue dans une liste non ordonnée.
/app/dashboard/components/TagCloud.tsx
export const ServerComponent = async ()=>{ const posts = await getSomeData() // call any nodejs api or server function during the component rendering // Don't even think about it. No useEffects are allowed here x_x const pasta = await getPasta() return ( <ul> { data.map(d=>( <li>{d.title}</li> )) } </ul> ) }
Le composant côté serveur TagCloud récupère puis restitue tous les noms de balises qui ont été utilisés dans le contenu des notes avec leur nombre respectif.
/app/dashboard/components/NotesSummary.tsx
"use client" import React,{useEffect,useState} from "react" export const ClientComponent = ()=>{ const [value,setValue] = useState() useEffect(()=>{ alert("Component have mounted!") return ()=>{ alert("Component is unmounted") } },[]) //.......... return ( <> <button onClick={()=>alert("Hello, from browser")}></button> {/* .......... JSX Code ...............*/} </> ) }
Le composant serveur NotesSummary restitue les informations récapitulatives après les avoir récupérées à l'aide de la fonction de cas d'utilisation getNoteSummary.
Passons maintenant à la page de profil où nous passerons en revue une fonctionnalité nextjs intéressante appelée Routes parallèles.
Les itinéraires parallèles nous permettent de simultanément ou conditionnellement restituer une ou plusieurs pages dans la même mise en page.
Dans notre exemple ci-dessous, nous allons afficher la page d'informations utilisateur et la page de notes utilisateur dans la même mise en page qui est /app/profile .
Vous pouvez créer des itinéraires parallèles en utilisant des emplacements nommés. Un emplacement nommé est déclaré exactement comme une sous-page mais le symbole @ doit précéder le nom du dossier contrairement aux pages ordinaires.
Par exemple, dans le dossier /app/profile/, nous allons créer deux emplacements nommés :
Créons maintenant un fichier de mise en page /app/profile/layout.tsx qui définira la mise en page de notre page /profile.
- app/ - notes/ --------------------------------> Server Side Caching Features - components/ - NotesList.tsx - [noteId]/ - actions/ -------------------------> Server Actions feature - delete-note.action.ts - edit-note.action.ts - components/ - DeleteButton.tsx - page.tsx - edit/ - components/ - EditNoteForm.tsx - page.tsx - loading.tsx --------------------> Page level Streaming feature - create/ - actions/ - create-note.action.ts - components/ - CreateNoteForm.tsx - page.tsx - error-page/ - page.tsx - error.tsx --------------------------> Error Boundary as a page feature - dashboard/ ---------------------------> Component Level Streaming Feature - components/ - NoteActivity.tsx - TagCloud.tsx - NotesSummary.tsx - page.tsx - profile/ ----------------------------->[6] Parallel Routes Feature - layout.tsx - page.tsx - @info/ - page.tsx - loading.tsx - @notes/ - page.tsx - loading.tsx - core/ --------------------------> Our business logic lives here - entities/ - note.ts - use-cases/ - create-note.use-case.ts - update-note.use-case.ts - delete-note.use-case.ts - get-note.use-case.ts - get-notes.use-case.ts - get-notes-summary.use-case.ts - get-recent-activity.use-case.ts - get-recent-tags.use-case.ts
Comme vous pouvez le voir dans le code ci-dessus, nous avons maintenant accès aux paramètres info et notes qui contiennent le contenu des pages @info et @notes.
Ainsi, la page @info sera rendue à gauche et les @notes seront rendues à droite.
Le contenu de page.tsx (référencé par enfants) sera rendu en bas de page.
/app/profile/@info/page.tsx
export const ServerComponent = async ()=>{ const posts = await getSomeData() // call any nodejs api or server function during the component rendering // Don't even think about it. No useEffects are allowed here x_x const pasta = await getPasta() return ( <ul> { data.map(d=>( <li>{d.title}</li> )) } </ul> ) }
La UserInfoPage est un composant serveur qui récupérera les informations utilisateur à l'aide de la fonction de cas d'utilisation getUserInfo.
Le squelette de secours ci-dessus sera envoyé au navigateur de l'utilisateur lorsque le composant récupère les données et est rendu sur le serveur (Server Side Streaming).
/app/profile/@info/loading.tsx
"use client" import React,{useEffect,useState} from "react" export const ClientComponent = ()=>{ const [value,setValue] = useState() useEffect(()=>{ alert("Component have mounted!") return ()=>{ alert("Component is unmounted") } },[]) //.......... return ( <> <button onClick={()=>alert("Hello, from browser")}></button> {/* .......... JSX Code ...............*/} </> ) }
La même chose s'applique au composant côté serveur LastNotesPage. il récupérera les données et les rendra sur le serveur pendant qu'une interface utilisateur squelette sera affichée à l'utilisateur
/app/profile/@notes/page.tsx
import { ClientComponent } from '@/components' // Allowed :) export const ServerComponent = ()=>{ return ( <> <ClientComponent/> </> ) }
/app/profile/@notes/loading.tsx
"use client" import { ServerComponent } from '@/components' // Not allowed :( export const ClientComponent = ()=>{ return ( <> <ServerComponent/> </> ) }
Explorons maintenant une fonctionnalité plutôt intéressante de Nextjs, la page error.tsx.
Lorsque vous déployez votre application en production, vous souhaiterez sûrement afficher une erreur conviviale lorsqu'une erreur non détectée est générée depuis l'une de vos pages.
C'est là qu'intervient le fichier error.tsx.
Créons d'abord un exemple de page qui génère une erreur non détectée après quelques secondes.
/app/error-page/page.tsx
import {ClientComponent} from '@/components/...' import {ServerComponent} from '@/components/...' export const ParentServerComponent = ()=>{ return ( <> <ClientComponent> <ServerComponent/> </ClientComponent> </> ) }
Lorsque la page est en veille ou en attente que la fonction de veille soit exécutée. La page de chargement ci-dessous sera présentée à l'utilisateur.
/app/error-page/loading.tsx
export const Component = ()=>{ const serverActionFunction = async(params:any)=>{ "use server" // server code lives here //... / } const handleClick = ()=>{ await serverActionFunction() } return <button onClick={handleClick}>click me</button> }
Après quelques secondes, l'erreur sera générée et supprimera votre page :(.
Pour éviter cela nous allons créer le fichier error.tsx qui exporte un composant qui fera office de Boundary d'erreur pour le /app/error-page/page .tsx.
/app/error-page/error.tsx
- app/ - notes/ --------------------------------> Server Side Caching Features - components/ - NotesList.tsx - [noteId]/ - actions/ -------------------------> Server Actions feature - delete-note.action.ts - edit-note.action.ts - components/ - DeleteButton.tsx - page.tsx - edit/ - components/ - EditNoteForm.tsx - page.tsx - loading.tsx --------------------> Page level Streaming feature - create/ - actions/ - create-note.action.ts - components/ - CreateNoteForm.tsx - page.tsx - error-page/ - page.tsx - error.tsx --------------------------> Error Boundary as a page feature - dashboard/ ---------------------------> Component Level Streaming Feature - components/ - NoteActivity.tsx - TagCloud.tsx - NotesSummary.tsx - page.tsx - profile/ ----------------------------->[6] Parallel Routes Feature - layout.tsx - page.tsx - @info/ - page.tsx - loading.tsx - @notes/ - page.tsx - loading.tsx - core/ --------------------------> Our business logic lives here - entities/ - note.ts - use-cases/ - create-note.use-case.ts - update-note.use-case.ts - delete-note.use-case.ts - get-note.use-case.ts - get-notes.use-case.ts - get-notes-summary.use-case.ts - get-recent-activity.use-case.ts - get-recent-tags.use-case.ts
Dans ce guide, nous avons exploré les fonctionnalités clés de Next.js en créant une application de notes pratiques. Nous avons couvert :
En appliquant ces concepts dans un projet réel, nous avons acquis une expérience pratique des puissantes capacités de Next.js. N'oubliez pas que la meilleure façon de consolider votre compréhension est la pratique.
Si vous avez des questions ou souhaitez discuter davantage de quelque chose, n'hésitez pas à me contacter ici.
Bon codage !
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!