Warum Next.js und Hasura zusammenpassen
2020 war das Jahr, in dem der JamStack-Ansatz im Frontend-Engineering endgueltig angekommen ist. Zwei Tools stechen dabei besonders hervor: Next.js (damals v10) als React-Framework mit Server-Side Rendering und Hasura als Instant-GraphQL-Engine ueber PostgreSQL.
Die Kombination loest ein konkretes Problem: Wer eine React-Anwendung mit typisierter GraphQL-API und SSR braucht, muss normalerweise ein eigenes Backend schreiben, Resolver definieren und sich um Schema-Synchronisation kuemmern. Hasura generiert die gesamte GraphQL-API automatisch aus dem Datenbank-Schema. Das spart bei einem typischen MVP zwei bis drei Wochen Backend-Entwicklung.
In diesem Artikel bauen wir eine vollstaendige SSR-Anwendung mit Next.js + TypeScript + Apollo Client + Hasura. Das Beispielprojekt ist ein Hundeheim mit drei Seiten: Startseite, Uebersicht (/dogs) und Detailansicht (/dog/[id]). Der gesamte Code liegt im Repository auf GitHub.
USEO’s Take
Bei USEO haben wir diese Kombination in mehreren Kundenprojekten evaluiert. Unser Fazit: Next.js + Hasura eignet sich hervorragend fuer datengetriebene MVPs und interne Tools, bei denen die Geschaeftslogik ueberschaubar ist. Sobald komplexe Autorisierung, mehrstufige Validierungen oder Workflows ins Spiel kommen, stoesst Hasura an Grenzen. In solchen Faellen greifen wir auf ein klassisches Ruby on Rails Backend mit graphql-ruby zurueck.
Ein konkretes Beispiel: GraphQL-Queries ueber Hasura liefern bei einfachen Reads (eine Tabelle, 10-50 Zeilen) Antwortzeiten von 15-40ms. Bei Joins ueber drei oder mehr Tabellen mit Filtern steigen die Zeiten auf 80-200ms, je nach Datenbank-Indexierung. Fuer die meisten Anwendungsfaelle ist das voellig ausreichend.
Was wir aufbauen
Inhaltsverzeichnis
- Next.js + TypeScript-Anwendung einrichten
- Grundlegende Einrichtung
- TypeScript hinzufuegen
- Routen definieren
- Hasura-Backend konfigurieren
- Next.js mit Hasura verbinden
- Umgebungsvariablen
- Apollo-Konfiguration
- Daten mit
useQueryabrufen - Server-seitiger Datenabruf
- Deployment auf Vercel
Nuetzliche Links
1. Next.js + TypeScript-Anwendung einrichten
1.1 Grundlegende Einrichtung
Wir starten das Projekt manuell statt mit npx create-next-app, um jeden Schritt nachvollziehen zu koennen. Bei einer produktiven Anwendung wuerde ich empfehlen, den Generator zu verwenden.
npm init -y
npm install react react-dom next
Die .gitignore braucht mindestens den node_modules-Ordner:
# .gitignore
/node_modules
In der package.json fuegen wir die Next.js-Skripte hinzu:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
}
}
Next.js verwendet das pages-Verzeichnis fuer dateibasiertes Routing. Jede Datei in pages/ wird automatisch zu einer Route. Wir erstellen die Startseite als pages/index.jsx:
// pages/index.jsx
import React from 'react'
const Index = () => {
return (
<div>
<h1>Homepage</h1>
</div>
)
}
export default Index
Kurzer Test mit npm run dev, ob alles laeuft, bevor wir TypeScript einrichten.
1.2 TypeScript hinzufuegen
Next.js erkennt TypeScript automatisch, sobald eine tsconfig.json existiert:
touch tsconfig.json
npm run dev
Beim naechsten Start zeigt die Konsole, welche Pakete fehlen:
npm install --save-dev typescript @types/react @types/node
Nach der Installation benennen wir index.jsx in index.tsx um und fuegen den Typ React.FC hinzu:
// pages/index.tsx
import React from 'react';
const Index: React.FC = () => {
return (
<div>
<h1>Homepage</h1>
</div>
);
};
export default Index;
1.3 Routen definieren
Unsere Anwendung braucht drei Seiten. Die Uebersichtsseite /dogs:
// pages/dogs.tsx
import React from 'react';
const Dogs: React.FC = () => {
return (
<div>
<h1>Alle Hunde</h1>
</div>
);
};
export default Dogs;
Und die Detailseite /dog/[id] mit dynamischem Routing. Next.js nutzt eckige Klammern fuer dynamische URL-Parameter:
// pages/dog/[id].tsx
import React from 'react';
import { useRouter } from 'next/router';
const Dog: React.FC = () => {
const router = useRouter();
const { id } = router.query;
return (
<div>
<h2>{`Hund: ${id}`}</h2>
</div>
);
};
export default Dog;
Die URL dog-shelter.com/dog/f1297fb9-... liefert die UUID als id-Parameter in der Komponente.
2. Wie Hasura die Backend-Arbeit ersetzt
Hasura generiert eine komplette GraphQL-API direkt aus einer PostgreSQL-Datenbank. Das bedeutet: Tabelle anlegen, fertig. Keine Resolver schreiben, kein Schema manuell pflegen.
Setup in drei Schritten
- Account auf cloud.hasura.io erstellen
- “Try a free database with Heroku” waehlen (Heroku stellt hier die PostgreSQL-Instanz bereit)
- Unter “Data” eine neue Tabelle anlegen
Fuer unser Hundeheim erstellen wir eine Tabelle dogs mit folgenden Spalten:
| Spalte | Typ | Bemerkung |
|---|---|---|
id | UUID | Primary Key, auto-generiert |
name | Text | Name des Hundes |
age | Integer | Alter |
bio | Text | Beschreibung |
image | Text | Bild-URL |
created_at | Timestamp | Auto-generiert |
updated_at | Timestamp | Auto-generiert |
Sobald die Tabelle existiert, steht die GraphQL-API bereit. Ueber die integrierte GraphiQL-Oberflaeche koennen Queries direkt getestet werden, ohne zusaetzliche Tools.
Was Hasura nicht abdeckt
Hasura eignet sich fuer CRUD-Operationen und einfache Geschaeftslogik ueber Actions und Event-Triggers. Wer komplexe Validierungen, mehrstufige Workflows oder umfangreiche Autorisierungsregeln braucht, muss einen eigenen Service dahinter schalten. Fuer dieses Tutorial reicht der Standard-Funktionsumfang.
3. Next.js mit Hasura verbinden
3.1 Umgebungsvariablen
Next.js unterstuetzt .env-Dateien nativ. Variablen mit dem Praefix NEXT_PUBLIC_ sind sowohl server- als auch clientseitig verfuegbar:
# .env
NEXT_PUBLIC_API_HOST=abc.hasura.app/v1/graphql
Wichtig: Die .env-Datei gehoert in die .gitignore, damit Zugangsdaten nicht ins Repository gelangen.
3.2 Apollo Client konfigurieren
Als GraphQL-Client verwenden wir Apollo Client 3.x:
npm install --save @apollo/client graphql
Die Konfiguration erfolgt in _app.tsx, der zentralen App-Komponente von Next.js. Der ApolloProvider stellt den Client allen untergeordneten Komponenten zur Verfuegung:
// pages/_app.tsx
import React from 'react';
import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client';
import { AppProps } from 'next/app';
const client = new ApolloClient({
uri: `https://${process.env.NEXT_PUBLIC_API_HOST}`,
cache: new InMemoryCache(),
});
const App = ({ Component, pageProps }: AppProps) => (
<ApolloProvider client={client}>
<Component {...pageProps} />
</ApolloProvider>
);
export default App;
InMemoryCache sorgt dafuer, dass identische Queries nicht mehrfach an den Server gehen. Bei einer Anwendung mit haeufig wechselnden Daten kann das Caching-Verhalten ueber fetchPolicy angepasst werden.
3.3 Daten mit useQuery abrufen
Der useQuery-Hook von Apollo gibt drei Werte zurueck: loading, error und data. Die GraphQL-Query kann vorab in der GraphiQL-Oberflaeche von Hasura getestet werden:
// pages/dogs.tsx
import { gql, useQuery } from '@apollo/client';
const GET_DOGS = gql`
query GetDogs {
dogs {
id
name
age
bio
}
}
`;
type Dog = {
id: string;
name: string;
age: number;
bio: string;
};
type DogsResponseData = {
dogs: Dog[];
};
const Dogs = () => {
const { loading, error, data } = useQuery<DogsResponseData>(GET_DOGS);
if (loading) return <p>Laden...</p>;
if (error) return <p>Fehler: {error.message}</p>;
return (
<div>
<h1>Hunde im Tierheim</h1>
{data.dogs.map((dog) => (
<ul key={dog.id}>
<li>{`Name: ${dog.name}`}</li>
<li>{`Alter: ${dog.age}`}</li>
<li>{`Bio: ${dog.bio}`}</li>
</ul>
))}
</div>
);
};
export default Dogs;
Hinweis: Das Verwenden von dog.id als Key statt eines Index-Werts ist die empfohlene Praxis in React. Es vermeidet Rendering-Probleme bei Listen, die sich aendern.
3.4 Server-seitiger Datenabruf mit getInitialProps
Bisher laeuft der Datenabruf clientseitig: Die Seite wird geladen, der Browser fuehrt die Query aus, und waehrend der Wartezeit sieht der Nutzer “Laden…”. Mit Server-Side Rendering verschwindet dieses Problem.
// pages/dogs.tsx
import { ApolloClient, NormalizedCacheObject } from '@apollo/client';
import { NextPageContext } from 'next';
type ServerSideProps = NextPageContext & {
apolloClient: ApolloClient<NormalizedCacheObject>;
};
type DogsResponse = {
dogs: Dog[];
};
Dogs.getInitialProps = async ({ apolloClient }: ServerSideProps) => {
const response = await apolloClient.query<DogsResponse>({
query: GET_DOGS,
});
return {
dogs: response.data.dogs,
};
};
export default withApollo({ ssr: true })(Dogs);
Der Unterschied fuer den Nutzer: Die Seite kommt komplett gerendert vom Server. Kein Ladeindikator, kein Layout-Shift. Fuer Suchmaschinen ist das ebenfalls relevant, da der Inhalt direkt im HTML vorhanden ist.
4. Deployment auf Vercel
Vercel ist die Hosting-Plattform der Next.js-Macher und entsprechend gut integriert. Das Deployment funktioniert ueber die CLI:
npm i -g vercel
vercel
Nach dem ersten Setup muss die Umgebungsvariable NEXT_PUBLIC_API_HOST in den Vercel-Projekteinstellungen unter “Environment Variables” hinterlegt werden. Danach deployt Vercel bei jedem Git-Push automatisch.
Wann sich dieses Setup lohnt
Diese Architektur (Next.js + Hasura + Vercel) eignet sich besonders fuer:
- MVPs und Prototypen, bei denen Geschwindigkeit in der Entwicklung Prioritaet hat
- Interne Tools und Dashboards, die primaer Daten lesen und anzeigen
- Kleinere Anwendungen mit ueberschaubarer Geschaeftslogik
Fuer groessere Produktivanwendungen empfehle ich, frueh ueber die Grenzen von Hasura nachzudenken: Wann brauchen wir einen eigenen Service fuer Geschaeftslogik? Wie handhabt Hasura Migrationen im Team? Diese Fragen frueh zu klaeren spart spaeter Aufwand.
Wie es weitergeht
In den naechsten Beitraegen erweitern wir das Hundeheim um CRUD-Operationen (Mutations), Echtzeit-Updates (Subscriptions) und eine richtige Benutzeroberflaeche. Das Repository wird laufend aktualisiert.