Optimieren von Next.js-Anwendungen mit Nx

Veröffentlicht: 2022-03-10
Kurze Zusammenfassung ↬ Nx ist ein Build-Framework, das die Optimierung, effiziente Skalierung von Anwendungen und andere Funktionen wie gemeinsam genutzte Bibliotheken und Komponenten erleichtert. In diesem Artikel sehen wir uns an, wie wir Next.js-Anwendungen mithilfe von Nx effektiv skalieren können.

In diesem Artikel erfahren Sie, wie Sie mithilfe von Nx und seinen umfangreichen Funktionen eine leistungsstarke Next.js-Anwendung optimieren und erstellen. Wir gehen durch die Einrichtung eines Nx-Servers, das Hinzufügen eines Plugins zu einem bestehenden Server und das Konzept eines Monorepos mit einer praktischen Visualisierung.

Wenn Sie als Entwickler Anwendungen optimieren und anwendungsübergreifend wiederverwendbare Komponenten effektiv erstellen möchten, zeigt Ihnen dieser Artikel, wie Sie Ihre Anwendungen schnell skalieren und wie Sie mit Nx arbeiten. Um mitzumachen, benötigen Sie grundlegende Kenntnisse des Next.js-Frameworks und von TypeScript.

Was ist Nx?

Nx ist ein Open-Source-Build-Framework, das Sie beim Entwerfen, Testen und Erstellen in jeder Größenordnung unterstützt – es lässt sich nahtlos in moderne Technologien und Bibliotheken integrieren und bietet gleichzeitig eine robuste Befehlszeilenschnittstelle (CLI), Caching und Abhängigkeitsverwaltung. Nx bietet Entwicklern fortschrittliche CLI-Tools und Plugins für moderne Frameworks, Tests und Tools.

In diesem Artikel konzentrieren wir uns darauf, wie Nx mit Next.js-Anwendungen funktioniert. Nx bietet Standardtools zum Testen und Gestalten in Ihren Next.js-Anwendungen, wie Cypress, Storybook und styled-components. Nx erleichtert ein Monorepo für Ihre Anwendungen und erstellt einen Arbeitsbereich, der den Quellcode und die Bibliotheken mehrerer Anwendungen enthalten kann, sodass Sie Ressourcen zwischen Anwendungen gemeinsam nutzen können.

Warum Nx verwenden?

Nx bietet Entwicklern eine angemessene Menge an sofort einsatzbereiten Funktionen, einschließlich Boilerplates für End-to-End (E2E)-Tests Ihrer Anwendung, eine Styling-Bibliothek und ein Monorepo.

Die Verwendung von Nx bringt viele Vorteile mit sich, und wir werden einige davon in diesem Abschnitt durchgehen.

  • Graphbasierte Aufgabenausführung
    Nx verwendet verteilte graphbasierte Aufgabenausführung und Berechnungs-Caching, um Aufgaben zu beschleunigen. Das System plant Aufgaben und Befehle unter Verwendung eines Graphensystems, um zu bestimmen, welcher Knoten (dh Anwendung) jede Aufgabe ausführen soll. Dadurch wird die Ausführung von Anwendungen gehandhabt und die Ausführungszeit effizient optimiert.
  • Testen
    Nx bietet vorkonfigurierte Testwerkzeuge für Unit-Tests und E2E-Tests.
  • Caching
    Nx speichert auch das zwischengespeicherte Projektdiagramm. Dadurch kann es nur aktualisierte Dateien erneut analysieren. Nx verfolgt Dateien, die seit dem letzten Commit geändert wurden, und ermöglicht es Ihnen, nur diese Dateien zu testen, zu erstellen und Aktionen auszuführen. Dies ermöglicht eine ordnungsgemäße Optimierung, wenn Sie mit einer großen Codebasis arbeiten.
  • Abhängigkeitsdiagramm
    Mit dem visuellen Abhängigkeitsdiagramm können Sie untersuchen, wie Komponenten miteinander interagieren.
  • Cloud-Speicher
    Nx bietet auch Cloud-Speicher und GitHub-Integration, sodass Sie Links mit Teammitgliedern teilen können, um Projektprotokolle zu überprüfen.
  • Code-Sharing
    Das Erstellen einer neuen gemeinsam genutzten Bibliothek für jedes Projekt kann ziemlich anstrengend sein. Nx eliminiert diese Komplikation und gibt Ihnen die Möglichkeit, sich auf die Kernfunktionalität Ihrer App zu konzentrieren. Mit Nx können Sie Bibliotheken und Komponenten anwendungsübergreifend gemeinsam nutzen. Sie können sogar wiederverwendbaren Code zwischen Ihren Front-End- und Back-End-Anwendungen austauschen.
  • Unterstützung für Monorepos
    Nx bietet einen Arbeitsbereich für mehrere Anwendungen. Mit diesem Setup kann ein GitHub-Repository die Codequelle für verschiedene Anwendungen in Ihrem Arbeitsbereich enthalten.
Mehr nach dem Sprung! Lesen Sie unten weiter ↓

Nx für veröffentlichbare Bibliotheken

Mit Nx können Sie veröffentlichungsfähige Bibliotheken erstellen. Dies ist wichtig, wenn Sie Bibliotheken haben, die Sie außerhalb des Monorepos verwenden werden. In jedem Fall, in dem Sie organisatorische UI-Komponenten mit Nx Storybook-Integration entwickeln, erstellt Nx veröffentlichungsfähige Komponenten neben Ihren Geschichten. Die veröffentlichbaren Komponenten können diese Komponenten kompilieren, um ein Bibliothekspaket zu erstellen, das Sie in einer externen Registrierung bereitstellen können. Sie würden beim Generieren der Bibliothek die Option --publishable verwenden, im Gegensatz zu --buildable , das zum Generieren von Bibliotheken verwendet wird, die nur im Monorepo verwendet werden. Nx stellt die publizierbaren Bibliotheken nicht automatisch bereit; Sie können den Build über einen Befehl wie nx build mylib (wobei mylib der Name der Bibliothek ist), der dann ein optimiertes Bundle im Ordner dist / mylib erstellt, das in einer externen Registrierung bereitgestellt werden kann.

Nx bietet Ihnen die Möglichkeit, einen neuen Arbeitsbereich mit Next.js als Voreinstellung zu erstellen oder Next.js zu einem vorhandenen Arbeitsbereich hinzuzufügen.

Um einen neuen Arbeitsbereich mit Next.js als Voreinstellung zu erstellen, können Sie den folgenden Befehl verwenden:

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

Dieser Befehl erstellt einen neuen Nx-Arbeitsbereich mit einer Next.js-App namens „todo“ und mit styled-components als Stilbibliothek.

Dann können wir die Next.js-Anwendung mit dem folgenden Befehl zu einem vorhandenen Nx-Arbeitsbereich hinzufügen:

 npx nx g @nrwl/next:app

Erstellen einer Next.js- und Nx-Anwendung

Das Nx-Plugin für Next.js enthält Tools und Executors zum Ausführen und Optimieren einer Next.js-Anwendung. Um zu beginnen, müssen wir einen neuen Nx-Arbeitsbereich mit next als Voreinstellung erstellen:

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

Der obige Codeblock generiert einen neuen Nx-Arbeitsbereich und die Next.js-Anwendung. Wir werden aufgefordert, Nx Cloud zu verwenden. Für dieses Tutorial wählen wir „Nein“ und warten dann, bis unsere Abhängigkeiten installiert sind. Sobald das erledigt ist, sollten wir einen ähnlichen Dateibaum haben:

 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

Im apps -Ordner haben wir unsere Next.js-Anwendung „todo“ mit dem vorkonfigurierten E2E-Test für die To-Do-App. All dies wird automatisch mit dem leistungsstarken Nx-CLI-Tool generiert.

Um unsere App auszuführen, verwenden Sie den npx nx serve todo . Sobald Sie mit der Bereitstellung der App fertig sind, sollten Sie den folgenden Bildschirm sehen:

Die von Nx generierte Homepage für eine neue Anwendung
Nx Standardseite für eine neue Anwendung. (Große Vorschau)

Erstellen der API

An dieser Stelle haben wir den Arbeitsbereich eingerichtet. Als Nächstes erstellen wir die CRUD-API, die wir für die Next.js-Anwendung verwenden werden. Dazu verwenden wir Express; Um die Monorepo-Unterstützung zu demonstrieren, werden wir unseren Server als Anwendung im Arbeitsbereich erstellen. Zuerst müssen wir das Express-Plugin für Nx installieren, indem wir diesen Befehl ausführen:

 npm install --save-dev @nrwl/express

Sobald dies erledigt ist, können wir unsere Express-App im bereitgestellten Arbeitsbereich einrichten. Führen Sie den folgenden Befehl aus, um eine Express-App zu generieren:

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

Der Befehl nx g @nrwl/express:application generiert eine Express-Anwendung, an die wir zusätzliche Spezifikationsparameter übergeben können; um den Namen der Anwendung anzugeben, verwenden Sie das Flag --name ; Um die Front-End-Anwendung anzugeben, die die Express-App verwendet, übergeben Sie den Namen einer App in unserem Arbeitsbereich an --frontendProject . Für eine Express-App stehen einige weitere Optionen zur Verfügung. Wenn dies erledigt ist, haben wir eine aktualisierte Dateistruktur im apps -Ordner mit dem hinzugefügten todo-api Ordner.

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

Der todo-api Ordner ist ein Express-Boilerplate mit einer main.ts Eintragsdatei.

 /** * 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);

Wir werden unsere Routen in dieser App erstellen. Zu Beginn initialisieren wir ein Array von Objekten mit zwei Schlüssel-Wert-Paaren, item und id , direkt unter der App-Deklaration.

 /** * 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() }, ]; …

Als nächstes richten wir die Route zum Abrufen aller To-do-Listen unter app.get() :

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

Der obige Codeblock gibt den aktuellen Wert von todoArray . Anschließend haben wir Routen zum Erstellen, Aktualisieren und Entfernen von To-Do-Elementen aus dem Array.

 … 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', }); }); …

Um ein neues Aufgabenelement zu erstellen, benötigen wir lediglich den Wert des neuen Elements als Zeichenfolge. Wir generieren eine ID, indem wir die ID des letzten Elements im Array auf dem Server erhöhen. Um ein vorhandenes Element zu aktualisieren, übergeben wir den neuen Wert für das Element und die ID des zu aktualisierenden Elementobjekts. Auf dem Server würden wir jedes Element mit der forEach -Methode durchlaufen und das Element an der Stelle aktualisieren, an der die ID mit der mit der Anfrage gesendeten ID übereinstimmt. Um schließlich ein Element aus dem Array zu entfernen, würden wir die ID des zu entfernenden Elements mit der Anfrage senden; Dann filtern wir das Array und geben ein neues Array aller Elemente zurück, die nicht mit der ID übereinstimmen, die mit der Anfrage gesendet wurde, und weisen das neue Array der todoArray Variablen zu.

Hinweis: Wenn Sie im Anwendungsordner Next.js nachsehen, sollten Sie eine Datei proxy.conf.json mit der folgenden Konfiguration sehen:

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

Dadurch wird ein Proxy erstellt, der es allen API-Aufrufen ermöglicht, Routen, die mit /api übereinstimmen, auf den todo-api Server auszurichten.

Generieren von Next.js-Seiten mit Nx

In unserer Next.js-Anwendung generieren wir eine neue Seite, home und eine item-Komponente. Nx stellt uns ein CLI-Tool zur Verfügung, mit dem wir ganz einfach eine Seite erstellen können:

 npx nx g @nrwl/next:page home

Beim Ausführen dieses Befehls werden wir aufgefordert, die Stilbibliothek auszuwählen, die wir für die Seite verwenden möchten. Für diesen Artikel wählen wir styled-components . Voila! Unsere Seite ist erstellt. Um eine Komponente zu erstellen, führen npx nx g @nrwl/next:component todo-item ; Dadurch wird ein component mit der todo-item Komponente erstellt.

API-Verbrauch in der Next.js-Anwendung

In jedem To-Do-Element haben wir zwei Schaltflächen, um das To-Do-Element zu bearbeiten und zu löschen. Die asynchronen Funktionen, die diese Aktionen ausführen, werden als Requisiten von der Homepage übergeben.

 … 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> ); }

Für die Aktualisierungsfunktion haben wir eine Eingabe, die deaktiviert ist, wenn der Zustand isEditingItem false ist. Sobald auf die Schaltfläche „Bearbeiten“ geklickt wird, wird der Zustand „ isEditingItem “ auf „ true “ gesetzt und die Schaltfläche „Aktualisieren“ angezeigt. Hier wird die Eingabekomponente aktiviert und der Benutzer kann einen neuen Wert eingeben; Wenn auf die Schaltfläche „Aktualisieren“ geklickt wird, ruft sie die Funktion updateItem mit den übergebenen Parametern auf und isEditingItem zurück auf false .

In der home Komponente haben wir die asynchronen Funktionen, die die CRUD-Operation ausführen.

 … 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(); }, []); …

Im obigen Codeblock haben wir fetchItems , das todoArray vom Server zurückgibt. Dann haben wir die createItem Funktion, die einen String akzeptiert; Der Parameter ist der Wert des neuen To-Do-Elements. Die updateItem Funktion benötigt zwei Parameter, die ID des zu aktualisierenden Elements und den updatedItem Wert. Und die Funktion deleteItem entfernt das Element, das der übergebenen ID entspricht.

Um das Aufgabenelement zu items , ordnen wir den Status des Elements zu:

 … 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> ); …

Unser Server und Frontend sind nun eingerichtet. Wir können die API-Anwendung bedienen, indem wir npx nx serve todo-api ausführen, und für die Next.js-Anwendung führen wir npx nx serve todo . Klicken Sie auf die Schaltfläche „Weiter“, und Sie sehen eine Seite mit dem standardmäßigen To-Do-Element.

Die Aufgabenseite der Anwendung mit der standardmäßigen Aufgabenliste auf dem Server.
Die Seite mit den Todo-Elementen der App. (Große Vorschau)

Wir haben jetzt eine funktionierende Next.js- und Express-Anwendung, die in einem Arbeitsbereich zusammenarbeiten.

Nx verfügt über ein weiteres CLI-Tool, mit dem wir das Abhängigkeitsdiagramm unserer Anwendung in unserem Terminallauf anzeigen können. Führen npx nx dep-graph aus, und wir sollten einen Bildschirm ähnlich dem Bild unten sehen, der das Abhängigkeitsdiagramm unserer Anwendung darstellt.

Abhängigkeitsdiagramm, das die Beziehung zwischen Anwendungen im Arbeitsbereich zeigt
Anwendung dep-graph. (Große Vorschau)

Andere CLI-Befehle für Nx

  • nx list
    Listet die aktuell installierten Nx-Plugins auf.
  • nx migrate latest
    Aktualisiert die Pakete in package.json auf die neueste Version.
  • nx affected
    Führt die Aktion nur für die betroffenen oder geänderten Apps aus.
  • nx run-many --target serve --projects todo-api,todo
    Führt den Zielbefehl für alle aufgelisteten Projekte aus.

Fazit

Als allgemeinen Überblick über Nx hat dieser Artikel behandelt, was Nx bietet und wie es uns die Arbeit erleichtert. Wir haben auch die Einrichtung einer Next.js-Anwendung in einem Nx-Arbeitsbereich, das Hinzufügen eines Express-Plug-ins zu einem vorhandenen Arbeitsbereich und die Verwendung der Monorepo-Funktion zum Unterbringen von mehr als einer Anwendung in unserem Arbeitsbereich durchlaufen.

Den vollständigen Quellcode finden Sie im GitHub-Repository. Weitere Informationen zu Nx finden Sie in der Dokumentation oder in der Nx-Dokumentation für Next.js.