Optimiser les applications Next.js avec Nx

Publié: 2022-03-10
Résumé rapide ↬ Nx est un framework de construction qui facilite l'optimisation, la mise à l'échelle efficace des applications et d'autres fonctionnalités telles que les bibliothèques et composants partagés. Dans cet article, nous verrons comment nous pouvons faire évoluer efficacement les applications Next.js en utilisant Nx.

Dans cet article, nous verrons comment optimiser et créer une application Next.js hautes performances à l'aide de Nx et de ses fonctionnalités riches. Nous verrons comment configurer un serveur Nx, comment ajouter un plugin à un serveur existant et le concept d'un monorepo avec une visualisation pratique.

Si vous êtes un développeur cherchant à optimiser les applications et à créer efficacement des composants réutilisables dans toutes les applications, cet article vous montrera comment faire évoluer rapidement vos applications et comment travailler avec Nx. Pour suivre, vous aurez besoin de connaissances de base du framework Next.js et de TypeScript.

Qu'est-ce que Nx ?

Nx est un framework de construction open source qui vous aide à concevoir, tester et construire à n'importe quelle échelle - s'intégrant de manière transparente aux technologies et bibliothèques modernes, tout en fournissant une interface de ligne de commande (CLI) robuste, la mise en cache et la gestion des dépendances. Nx offre aux développeurs des outils CLI avancés et des plugins pour les frameworks, tests et outils modernes.

Pour cet article, nous nous concentrerons sur le fonctionnement de Nx avec les applications Next.js. Nx fournit des outils standard pour tester et styliser vos applications Next.js, telles que Cypress, Storybook et les composants de style. Nx facilite un monorepo pour vos applications, créant un espace de travail pouvant contenir le code source et les bibliothèques de plusieurs applications, vous permettant de partager des ressources entre les applications.

Pourquoi utiliser Nx ?

Nx fournit aux développeurs une quantité raisonnable de fonctionnalités prêtes à l'emploi, y compris des passe-partout pour les tests de bout en bout (E2E) de votre application, une bibliothèque de style et un monorepo.

De nombreux avantages découlent de l'utilisation de Nx, et nous en passerons en revue quelques-uns dans cette section.

  • Exécution de tâches basée sur des graphes
    Nx utilise l'exécution de tâches basée sur des graphes distribués et la mise en cache de calcul pour accélérer les tâches. Le système planifiera les tâches et les commandes à l'aide d'un système de graphes pour déterminer quel nœud (c'est-à-dire l'application) doit exécuter chaque tâche. Cela gère l'exécution des applications et optimise efficacement le temps d'exécution.
  • Essai
    Nx fournit des outils de test préconfigurés pour les tests unitaires et les tests E2E.
  • Mise en cache
    Nx stocke également le graphique du projet mis en cache. Cela lui permet de réanalyser uniquement les fichiers mis à jour. Nx garde une trace des fichiers modifiés depuis la dernière validation et vous permet de tester, de créer et d'effectuer des actions uniquement sur ces fichiers ; cela permet une optimisation appropriée lorsque vous travaillez avec une grande base de code.
  • Graphique de dépendance
    Le graphique de dépendance visuel vous permet d'inspecter la façon dont les composants interagissent les uns avec les autres.
  • Stockage en ligne
    Nx fournit également un stockage dans le cloud et une intégration GitHub, afin que vous puissiez partager des liens avec les membres de l'équipe pour consulter les journaux de projet.
  • Partage de Code
    La création d'une nouvelle bibliothèque partagée pour chaque projet peut être assez éprouvante. Nx élimine cette complication, vous permettant de vous concentrer sur les fonctionnalités de base de votre application. Avec Nx, vous pouvez partager des bibliothèques et des composants entre les applications. Vous pouvez même partager du code réutilisable entre vos applications front-end et back-end.
  • Prise en charge des monodépôts
    Nx fournit un espace de travail pour plusieurs applications. Avec cette configuration, un référentiel GitHub peut héberger le code source de diverses applications sous votre espace de travail.
Plus après saut! Continuez à lire ci-dessous ↓

Nx pour les bibliothèques publiables

Nx vous permet de créer des bibliothèques publiables. Ceci est essentiel lorsque vous avez des bibliothèques que vous utiliserez en dehors du monorepo. Dans tous les cas où vous développez des composants d'interface utilisateur organisationnels avec l'intégration de Nx Storybook, Nx créera des composants publiables à côté de vos histoires. Les composants publiables peuvent compiler ces composants pour créer un ensemble de bibliothèques que vous pouvez déployer sur un registre externe. Vous utiliserez l'option --publishable lors de la génération de la bibliothèque, contrairement à --buildable , qui est utilisée pour générer des bibliothèques utilisées uniquement dans le monorepo. Nx ne déploie pas automatiquement les bibliothèques publiables ; vous pouvez invoquer le build via une commande telle que nx build mylib (où mylib est le nom de la bibliothèque), qui produira alors un bundle optimisé dans le dossier dist / mylib pouvant être déployé sur un registre externe.

Nx vous donne la possibilité de créer un nouvel espace de travail avec Next.js comme préréglage ou d'ajouter Next.js à un espace de travail existant.

Pour créer un nouvel espace de travail avec Next.js comme préréglage, vous pouvez utiliser la commande suivante :

 npx create-nx-workspace happynrwl \ --preset=next \ --style=styled-components \ --appName=todo

Cette commande créera un nouvel espace de travail Nx avec une application Next.js nommée "todo" et avec styled-components de style comme bibliothèque de style.

Ensuite, nous pouvons ajouter l'application Next.js à un espace de travail Nx existant avec la commande suivante :

 npx nx g @nrwl/next:app

Construire une application Next.js et Nx

Le plug-in Nx pour Next.js comprend des outils et des exécuteurs pour exécuter et optimiser une application Next.js. Pour commencer, nous devons créer un nouvel espace de travail Nx avec next comme préréglage :

 npx create-nx-workspace happynrwl \ --preset=next \ --style=styled-components \ --appName=todo

Le bloc de code ci-dessus générera un nouvel espace de travail Nx et l'application Next.js. Nous recevrons une invite pour utiliser Nx Cloud. Pour ce tutoriel, nous sélectionnerons "Non", puis attendrons l'installation de nos dépendances. Une fois cela fait, nous devrions avoir une arborescence de fichiers similaire à celle-ci :

 happynrwl ┣ apps ┃ ┣ todo ┃ ┣ todo-e2e ┃ ┗ .gitkeep ┣ libs ┣ node_modules ┣ tools ┣ .editorconfig ┣ .eslintrc.json ┣ .gitignore ┣ .prettierignore ┣ .prettierrc ┣ README.md ┣ babel.config.json ┣ jest.config.js ┣ jest.preset.js ┣ nx.json ┣ package-lock.json ┣ package.json ┣ tsconfig.base.json ┗ workspace.json

Dans le dossier apps , nous aurons notre application Next.js "todo", avec le test E2E préconfiguré pour l'application to-do. Tout cela est généré automatiquement avec le puissant outil Nx CLI.

Pour exécuter notre application, utilisez la npx nx serve todo . Une fois que vous avez terminé de servir l'application, vous devriez voir l'écran ci-dessous :

La page d'accueil générée par Nx pour une nouvelle application
Nx page par défaut pour une nouvelle application. ( Grand aperçu )

Construire l'API

À ce stade, nous avons configuré l'espace de travail. La prochaine étape consiste à créer l'API CRUD que nous utiliserons sur l'application Next.js. Pour ce faire, nous utiliserons Express; pour démontrer la prise en charge de monorepo, nous allons construire notre serveur en tant qu'application dans l'espace de travail. Tout d'abord, nous devons installer le plugin Express pour Nx en exécutant cette commande :

 npm install --save-dev @nrwl/express

Une fois cela fait, nous sommes prêts à configurer notre application Express dans l'espace de travail fourni. Pour générer une application Express, exécutez la commande ci-dessous :

 npx nx g @nrwl/express:application --name=todo-api --frontendProject=todo

La commande nx g @nrwl/express:application va générer une application Express à laquelle nous pouvons passer des paramètres de spécification supplémentaires ; pour spécifier le nom de l'application, utilisez l'indicateur --name ; pour indiquer l'application frontale qui utilisera l'application Express, transmettez le nom d'une application dans notre espace de travail à --frontendProject . Quelques autres options sont disponibles pour une application Express. Lorsque cela sera fait, nous aurons une structure de fichiers mise à jour dans le dossier des apps avec le dossier todo-api ajouté.

 happynrwl ┣ apps ┃ ┣ todo ┃ ┣ todo-api ┃ ┣ todo-e2e ┃ ┗ .gitkeep …

Le dossier todo-api est un passe-partout Express avec un fichier d'entrée main.ts

 /** * This is not a production server yet! * This is only minimal back end to get started. */ import * as express from 'express'; import {v4 as uuidV4} from 'uuid'; const app = express(); app.use(express.json()); // used instead of body-parser app.get('/api', (req, res) => { res.send({ message: 'Welcome to todo-api!' }); }); const port = process.env.port || 3333; const server = app.listen(port, () => { console.log(`Listening at http://localhost:${port}/api`); }); server.on('error', console.error);

Nous allons créer nos itinéraires à l'intérieur de cette application. Pour commencer, nous allons initialiser un tableau d'objets avec deux paires clé-valeur, item et id , juste sous la déclaration de l'application.

 /** * This is not a production server yet! * This is only minimal back end to get started. */ import * as express from 'express'; import {v4 as uuidV4} from 'uuid'; const app = express(); app.use(express.json()); // used instead of body-parser let todoArray: Array<{ item: string; id: string }> = [ { item: 'default todo', id: uuidV4() }, ]; …

Ensuite, nous allons configurer la route pour récupérer toutes les listes de tâches sous app.get() :

 … app.get('/api', (req, res) => { res.status(200).json({ data: todoArray, }); }); …

Le bloc de code ci-dessus renverra la valeur actuelle de todoArray . Par la suite, nous aurons des itinéraires pour créer, mettre à jour et supprimer des éléments à faire du tableau.

 … app.post('/api', (req, res) => { const item: string = req.body.item; // Increment ID of item based on the ID of the last item in the array. let id: string = uuidV4(); // Add the new object to the array todoArray.push({ item, id }); res.status(200).json({ message: 'item added successfully', }); }); app.patch('/api', (req, res) => { // Value of the updated item const updatedItem: string = req.body.updatedItem; // ID of the position to update const id: string = req.body.id; // Find index of the ID const arrayIndex = todoArray.findIndex((obj) => obj.id === id); // Update item that matches the index todoArray[arrayIndex].item = updatedItem res.status(200).json({ message: 'item updated successfully', }); }); app.delete('/api', (req, res) => { // ID of the position to remove const id: string = req.body.id; // Update array and remove the object that matches the ID todoArray = todoArray.filter((val) => val.id !== id); res.status(200).json({ message: 'item removed successfully', }); }); …

Pour créer un nouvel élément de tâche, tout ce dont nous avons besoin est la valeur du nouvel élément sous forme de chaîne. Nous allons générer un ID en incrémentant l'ID du dernier élément du tableau sur le serveur. Pour mettre à jour un élément existant, nous transmettons la nouvelle valeur de l'élément et l'ID de l'objet élément à mettre à jour ; sur le serveur, nous ferions une boucle sur chaque élément avec la méthode forEach et mettrions à jour l'élément à l'endroit où l'ID correspond à l'ID envoyé avec la requête. Enfin, pour supprimer un élément du tableau, nous envoyons l'ID de l'élément à supprimer avec la requête ; ensuite, nous filtrons à travers le tableau et renvoyons un nouveau tableau de tous les éléments ne correspondant pas à l'ID envoyé avec la requête, en attribuant le nouveau tableau à la variable todoArray .

Remarque : Si vous regardez dans le dossier de l'application Next.js, vous devriez voir un fichier proxy.conf.json avec la configuration ci-dessous :

 { "/api": { "target": "http://localhost:3333", "secure": false } }

Cela crée un proxy, permettant à tous les appels d'API vers les routes correspondant à /api de cibler le serveur todo-api .

Génération de pages Next.js avec Nx

Dans notre application Next.js, nous allons générer une nouvelle page, home et un composant item. Nx fournit un outil CLI pour créer facilement une page :

 npx nx g @nrwl/next:page home

Lors de l'exécution de cette commande, nous recevrons une invite pour sélectionner la bibliothèque de style que nous voulons utiliser pour la page ; pour cet article, nous sélectionnerons styled-components . Voila ! Notre page est créée. Pour créer un composant, exécutez npx nx g @nrwl/next:component todo-item ; cela créera un dossier de component avec le composant todo-item .

Consommation d'API dans l'application Next.js

Dans chaque élément à faire, nous aurons deux boutons, pour modifier et supprimer l'élément à faire. Les fonctions asynchrones effectuant ces actions sont transmises en tant qu'accessoires depuis la page d'accueil.

 … export interface TodoItemProps { updateItem(id: string, updatedItem: string): Promise<void>; deleteItem(id: string): Promise<void>; fetchItems(): Promise<any>; item: string; id: string; } export const FlexWrapper = styled.div` width: 100%; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #ccc; padding-bottom: 10px; margin-top: 20px; @media all and (max-width: 470px) { flex-direction: column; input { width: 100%; } button { width: 100%; } } `; export function TodoItem(props: TodoItemProps) { const [isEditingItem, setIsEditingItem] = useState<boolean>(false); const [item, setNewItem] = useState<string | null>(null); return ( <FlexWrapper> <Input disabled={!isEditingItem} defaultValue={props.item} isEditing={isEditingItem} onChange={({ target }) => setNewItem(target.value)} /> {!isEditingItem && <Button onClick={() => setIsEditingItem(true)} > Edit </Button>} {isEditingItem && <Button onClick={async () => { await props.updateItem(props.id, item); //fetch updated items await props.fetchItems(); setIsEditingItem(false) }}> Update </Button>} <Button danger onClick={async () => { await props.deleteItem(props.id); //fetch updated items await await props.fetchItems(); }} > Delete </Button> </FlexWrapper> ); }

Pour la fonctionnalité de mise à jour, nous avons une entrée qui est désactivée lorsque l'état isEditingItem est false . Une fois que le bouton "Modifier" est cliqué, il bascule l'état isEditingItem sur true et affiche le bouton "Mettre à jour". Ici, le composant d'entrée est activé et l'utilisateur peut entrer une nouvelle valeur ; lorsque le bouton "Mettre à jour" est cliqué, il appelle la fonction updateItem avec les paramètres transmis, et il bascule isEditingItem à false .

Dans le composant de la page home , nous avons les fonctions asynchrones effectuant l'opération CRUD.

 … const [items, setItems] = useState<Array<{ item: string; id: string }>>([]); const [newItem, setNewItem] = useState<string>(''); const fetchItems = async () => { try { const data = await fetch('/api/fetch'); const res = await data.json(); setItems(res.data); } catch (error) { console.log(error); } }; const createItem = async (item: string) => { try { const data = await fetch('/api', { method: 'POST', body: JSON.stringify({ item }), headers: { 'Content-Type': 'application/json', }, }); } catch (error) { console.log(error); } }; const deleteItem = async (id: string) => { try { const data = await fetch('/api', { method: 'DELETE', body: JSON.stringify({ id }), headers: { 'Content-Type': 'application/json', }, }); const res = await data.json(); alert(res.message); } catch (error) { console.log(error); } }; const updateItem = async (id: string, updatedItem: string) => { try { const data = await fetch('/api', { method: 'PATCH', body: JSON.stringify({ id, updatedItem }), headers: { 'Content-Type': 'application/json', }, }); const res = await data.json(); alert(res.message); } catch (error) { console.log(error); } }; useEffect(() => { fetchItems(); }, []); …

Dans le bloc de code ci-dessus, nous avons fetchItems , qui renvoie todoArray depuis le serveur. Ensuite, nous avons la fonction createItem , qui prend une chaîne ; le paramètre est la valeur du nouvel élément de tâche. La fonction updateItem prend deux paramètres, l'ID de l'élément à mettre à jour et la valeur updatedItem . Et la fonction deleteItem supprime l'élément correspondant à l'ID transmis.

Pour afficher l'élément à faire, nous mappons l'état des items :

 … return ( <StyledHome> <h1>Welcome to Home!</h1> <TodoWrapper> {items.length > 0 && items.map((val) => ( <TodoItem key={val.id} item={val.item} id={val.id} deleteItem={deleteItem} updateItem={updateItem} fetchItems={fetchItems} /> ))} </TodoWrapper> <form onSubmit={async(e) => { e.preventDefault(); await createItem(newItem); //Clean up new item setNewItem(''); await fetchItems(); }} > <FlexWrapper> <Input value={newItem} onChange={({ target }) => setNewItem(target.value)} placeholder="Add new item…" /> <Button success type="submit"> Add + </Button> </FlexWrapper> </form> </StyledHome> ); …

Notre serveur et notre frontal sont maintenant configurés. Nous pouvons servir l'application API en exécutant npx nx serve todo-api , et pour l'application Next.js, nous npx nx serve todo . Cliquez sur le bouton "Continuer" et vous verrez une page avec l'élément de tâche par défaut affiché.

La page des éléments à faire de l'application, avec la liste de tâches par défaut sur le serveur.
La page des éléments de tâches de l'application. ( Grand aperçu )

Nous avons maintenant une application Next.js et Express qui fonctionne ensemble dans un seul espace de travail.

Nx a un autre outil CLI qui nous permet de visualiser le graphique de dépendance de notre application dans notre exécution de terminal. Exécutez npx nx dep-graph , et nous devrions voir un écran similaire à l'image ci-dessous, représentant le graphique de dépendance de notre application.

Graphique de dépendance montrant la relation entre les applications sur l'espace de travail
Graphe de dép. d'application. ( Grand aperçu )

Autres commandes CLI pour Nx

  • nx list
    Répertorie les plugins Nx actuellement installés.
  • nx migrate latest
    Met à jour les packages dans package.json vers la dernière version.
  • nx affected
    Effectue l'action uniquement sur les applications affectées ou modifiées.
  • nx run-many --target serve --projects todo-api,todo
    Exécute la commande cible sur tous les projets répertoriés.

Conclusion

En tant que présentation générale de Nx, cet article a couvert ce que propose Nx et comment cela nous facilite le travail. Nous avons également expliqué comment configurer une application Next.js dans un espace de travail Nx, ajouter un plug-in Express à un espace de travail existant et utiliser la fonctionnalité monorepo pour héberger plusieurs applications dans notre espace de travail.

Vous trouverez le code source complet dans le référentiel GitHub. Pour plus d'informations sur Nx, consultez la documentation ou la documentation Nx pour Next.js.