triplecore-logotriplecore
  Zurück zur Blog-Übersicht

Erstellung einer Webseite im Post-SPA-Zeitalter

In den letzten Jahren haben Single-Page-Applikationen (SPA) zurecht eine große Beliebtheit erreicht. Gründe dafür sind unter anderen dem Nutzungsgefühl einer nativen App zu entsprechen und die Serverlast durch eine clientseitige Ausführung zu verringern. Ein weiterer Faktor ist natürlich, dass die in diesem Zusammenhang eingesetzten Frameworks auch die Developer Experience verbessern.
SPA bringen jedoch auch ein paar Nachteile mit sich, die bekanntesten davon sind, ein höherer Aufwand für SEO und lange Ladezeiten beim ersten Seitenaufruf. Um diese und andere Herausforderungen zu bewältigen, sind neue Frameworks entstanden. Eines dieser Frameworks ist Next.js.

Next.js verfolgt einen hybriden Ansatz, der sowohl Static Site Generation (SSG) als auch das Server-Side-Rendering (SSR) ermöglicht. Zudem verspricht das Framework Zero Configuration. Das trifft zwar nur dann zu, wenn die von Next.js vorgesehen Standards benutzt werden, aber in jedem Fall bietet es einen schnellen Einstieg.

Wir waren auf der Suche nach einem Framework, dass uns Static Site Generation einfach ermöglicht. Next.js ist hier nicht die einzige Option. Weitere populäre Frameworks sind Gatsby oder NuxtJS. Wir wollten React einsetzen, daher haben wir uns NuxtJS nicht intensiver angeguckt. Spannender ist der Vergleich mit Gatsby. Gatsby ist ein sehr umfangreiches Framework und bietet eine Vielzahl praktischer Out-of-the-box-Funktionen und Lösungen bspw. fürs Testen oder zum Nachladen von Bildern. Das ist vor allem dann praktisch, wenn man diese Funktionen nutzen will, andernfalls bringt das vor allem Ballast für die Anwendung – Next.js kommt dementsprechend schlanker daher.
Zwei weitere Punkte haben bei unserer Entscheidung auch eine Rolle gespielt:

  • Gatsby ist designed um Daten mit GraphQL abzufragen. GraphQL ist toll, aber für unsere Anforderung nicht das richtige.
  • Next.js ist richtig flott, sowohl beim lokalen Starten der Anwendung, als auch beim Build.

So ist unsere Wahl auf Next.js gefallen. Starten wir also mit dem praktischen Teil dieses Artikels.

Konfiguration für eine Next.js Anwendung mit TypeScript

Für das Setup deiner Next.js Anwendung bietet Vercel (die Macher von Next.js) auf GitHub eine Auswahl von Beispielen. In der README findet ihr den Befehl, um die Beispiele als Starter zu verwenden. Wir haben das Beispiel für Typescript ausgewählt. Also erzeugen wir ein neues Projekt mit dem Befehl:

1npx create-next-app --example with-typescript $PROJECT_NAME
2# or
3yarn create next-app --example with-typescript $PROJECT_NAME

Das Script erzeugt einen neuen Ordner mit der angegebenen Namen des Projekts. Wechsle wie vom Script vorgeschlagen in den Ordner und starte die Anwendung im Development Mode.

1cd $PROJECT_NAME
2yarn dev
3# or
4cd $PROJECT_NAME
5npm run dev

Ihr solltet nun im Browser unter http://localhost:3000 eure Seite sehen. Diese ist im Vergleich zum normalen Next.js-Starter bereits abgespeckt und erinnert an die frühen Anfänge des Internets.

Startseite der Anwendung

Der Starter kommt bereits mit ein paar Komponenten. Eine dieser Komponenten benutzen wir nun um sie in Storybook zu dokumentieren. Dafür machen wir sie zunächst etwas ansehnlicher. Wir benutzen dafür styled-jsx. Das bietet die Möglichkeit, das Styling unserer Komponenten bereits beim Build in das HTML unserer statischen Seiten zu integrieren. Zusätzlich können damit DesignTokens sehr einfach verwenden. styled-jsx ist eine DevDependency von Next.js und muss daher nicht separat installiert werden.

Integration von Storybook und styled-jsx

Storybook ist ein beliebtes Tool, um Komponenten, die bspw. mit React, Angular oder Vue entwickelt wurden zu dokumentieren und isoliert zu testen. Es kann verwendet werden um Komponenten über Teamgrenzen hinweg sichtbar zu machen. So können diese einfacher wiederverwendet werden und mehrfacher Implementierungsaufwand wird verhindert. Damit ist es häufig ein erster Schritt in Richtung Design-System.

Wir wollen gleich die List-Komponente dokumentieren. Öffne diese zunächst im Code-Editor deiner Wahl - /components/List.tsx und füge folgendes Snippet innerhalb des <ul>-Tags hinzu oder werde selbst kreativ.
Auf der Seite http://localhost:3000/users siehst du das Ergebnis.

1 <style jsx>{`
2 ul {
3 list-style: none;
4 margin: 0;
5 padding: 1rem 1rem 1rem 3rem;
6 background: #ebebeb;
7 font-family: "Verdana", sans-serif;
8 }
9 ul li:before {
10 content: ${`"\\25CF"`};
11 display: inline-block;
12 width: 1.5rem;
13 margin-left: -1.5rem;
14 color: #bfbfbf;
15 }
16 ul :global(a) {
17 color: #7f2a68; /* Ihr könnt auch eine andere Farbe nehmen, wir empfehlen lila. */
18 text-decoration: none;
19 }
20 ul :global(a:hover) {
21 box-shadow: inset 0 -2px 0 #7f2a68;
22 outline: none;
23 }
24 `}</style>

Weiter geht's mit der Integration von Storybook.

Mit dem Release der v6 ist Storybook auf den Low-Code-Zug aufgesprungen und bietet ebenfalls ein Zero-Config-Setup. Das klingt schon gut.
Noch besser ist der eingebaute Support für TypeScript 🤩.

Wechsle im Terminal in den Projekt-Ordner und führe folgenden Befehl aus.

1 npx sb init

Storybook wird dadurch in dein Projekt integriert und kann im Anschluss gestartet werden. Es werden sogar schon ein paar Beispiel-Stories angelegt. Mit den folgenden Befehl kannst du Storybook über das Terminal starten.

1 yarn storybook
2 # or
3 npm run storybook

Storybook hat die Beispiel-Dateien in einen eigenen Ordner (/stories) gepackt. Eine andere Möglichkeit wäre es, die Stories direkt im Ordner der Komponente zu erstellen. Wenn du diese Variante bevorzugst, musst du Next.js so konfigurieren, dass es beim Build die Stories ignoriert (Doku).

Wir bleiben bei der einfacheren Variante. Erzeuge dafür im /stories-Ordner eine neue Datei namens List.stories.tsx. Der Inhalt dieser Story könnte wie folgt aussehen.

1 import React from 'react';
2 import { Story, Meta } from '@storybook/react/types-6-0';
3 import List, { ListProps } from '../components/List';
4
5 export default {
6 title: 'List',
7 component: List,
8 } as Meta;
9
10 const Template: Story<ListProps> = (args) => <List {...args} />;
11
12 export const Primary = Template.bind({});
13 Primary.args = {
14 items: [{ id: 1, name: "Foo Bar"}]
15 };

Damit das funktioniert musst du noch eine kleine Änderung in der List-Komponente machen und die Props in ListProps umbenennen und exportieren.
Wenn du Storybook gestartet hast, kannst du nun die List-Komponente sehen.

Die Listkomponente in Storybook mit falschen Styles

Das ist schon mal nicht schlecht, aber die Links haben die falsche Farbe. Im HTML der Komponente siehst du, dass styled-jsx das CSS der Komponente nicht richtig interpretiert und lediglich ein <style>-Tag hinzugefügt wird. Das bedeutet, dass die Styles für Storybook nun global sind. Das kann zu ungewollten Nebeneffekten führen. Darüber hinaus wurden die Style-Regeln für den Link, die wir mit :global(a) definiert haben, ohne Veränderung übernommen. Da es sich hierbei aber nicht um valides CSS handelt, haben sie keine Funktion. Wenn du schon die Entwickler-Tools geöffnet hast, kannst du auch feststellen, dass Fehler in der Konsole aufgetaucht sind.

Das Style-Tag in den Developer Tools  Fehler in der Konsole

Also müssen wir Storybook styled-jsx beibringen. Um das zu tun, brauchen wir zwei kleine Anpassungen. Zunächst müssen wir Babel eine Konfiguration geben. Babel ist bereits im Einsatz. Dass wir bisher nichts von Babel mitbekommen haben, liegt am Low-Code-Ansatz von Next.js. Erstelle im /.storybook-Ordner die Datei .babelrc und füge folgende Konfiguration ein.

1{
2 "presets": [
3 ["next/babel"]
4 ],
5 "plugins": [
6 ["styled-jsx/babel", { "optimizeForSpeed": true, "sourceMaps": true }]
7 ]
8}

Jetzt ist Storybook schon mal darauf vorbereitet, dass styled-jsx benutzt wird. Trotzdem erscheint in Storybook im Browser noch folgender Fehler:

ReferenceError: _JSXStyle is not defined
    at List (http://localhost:6007/main.821735a75d880616e079.bundle.js:196:13)
    at renderWithHooks (webpack://storybook_docs_dll//Users/shilman/projects/baseline/storybook/node_modules/react-dom/cjs/react-dom.development.js?:2546:153)
    at mountIndeterminateComponent (webpack://storybook_docs_dll//Users/shilman/projects/baseline/storybook/node_modules/react-dom/cjs/react-dom.development.js?:2831:885)
    at beginWork (webpack://storybook_docs_dll//Users/shilman/projects/baseline/storybook/node_modules/react-dom/cjs/react-dom.development.js?:3049:101)
    at HTMLUnknownElement.callCallback (webpack://storybook_docs_dll//Users/shilman/projects/baseline/storybook/node_modules/react-dom/cjs/react-dom.development.js?:70:102)
    at Object.invokeGuardedCallbackDev 

Offenbar müssen wir Storybook noch mit _JSXStyle bekannt machen. Öffne dazu die preview.js im /.storybook-Ordner und füge folgendes Snippet hinzu.

1import _JSXStyle from 'styled-jsx/style';
2
3if (typeof global !== 'undefined') {
4 Object.assign(global, { _JSXStyle })
5}


Und fertig! 🥳 Im Browser siehst du nun die gestylte Komponente!Fehler in der Konsole

Bonus: Storybook mit Next.js Router verbinden

In der Konsole erscheint nun noch ein Fehler, der damit zusammenhängt, dass die Kinder der List-Komponente die Link -Komponente von Next.js verwenden. Das liegt daran, dass Storybook den Router von Next.js noch nicht kennt. Der Fehler ist in der Konsole zu sehen.

Uncaught TypeError: Cannot read property 'prefetch' of null
    at prefetch (link.js:7)
    at link.js:22
    at link.js:3
    at Array.forEach (<anonymous>)
    at IntersectionObserver.rootMargin (link.js:3)

Der einfachste Weg, diesen Fehler zu beheben, besteht darin ein zusätzliches Plugin für Storybook einzubinden. Das Plugin bietet die Möglichkeit, Komponenten mit Routing-Funktionalität besser zu testen. Über das Plugin kann der Router mit verschiedenen Routen und Einstellungen gemockt werden. Wenn du das möchtest, füge das Plugin als DevDependency hinzu.

1npm save storybook-addon-next-router --dev
2# or
3yarn add storybook-addon-next-router --dev

Nun brauchst du nur noch folgenden Code in der preview.js ergänzen.

1import { withNextRouter } from 'storybook-addon-next-router';
2import { addDecorator } from '@storybook/react';
3
4addDecorator(
5 withNextRouter({})
6);

Damit sind alle Fehler verschwunden und du kannst weitere Komponenten hinzufügen und dokumentieren.

Well done, Cowboy! 😉

eye candy
Kristina Mergenthaler - 16.10.2020