Next.js-Entwickler in Bielefeld

Next.js für Unternehmen in Bielefeld von einer Person: Händlerportale mit Rollen, Tabellen mit serverseitiger Paginierung, Beleg-Downloads und die Anbindung eines ERP. Mit Hinweisen der LDI NRW.

Next.js-Entwickler

Mein Platz in der WordPress-Community

  • Mein wordpress.org-Konto besteht seit 2006.
  • Ich habe zum WordPress-Core in den Versionen 7.0 und 7.1 beigetragen.
  • Ich betreue 30 Plugins auf wordpress.org.
  • Ich organisiere WordCamps und trete dort als Speaker auf.
  • Ich war bei jedem WordCamp Europe von 2017 bis 2026 dabei.

Mein WordPress.org-Profil

Mein Name ist Mariusz Szatkowski. Ich arbeite von Gdynia aus allein und seit 2006 mit WordPress. Für Unternehmen in Bielefeld beschreibe ich hier ein Next.js-Projekt, das in Ostwestfalen häufig gebraucht wird: ein Portal, in dem sich Händler oder Kunden anmelden, ihre Aufträge sehen und Belege laden, und interne Werkzeuge, die dasselbe für Mitarbeitende tun. Die Daten bleiben im ERP, Next.js zeigt sie an. Alle Angaben zu Next.js stammen aus der Dokumentation auf nextjs.org, Stand der Seiten November 2026. Die Codebeispiele habe ich mit Version 16.4.6 gebaut und gegen ein Platzhalter-ERP laufen lassen. Diese Version erschien laut npm-Registry am 9. November 2026.

#Warum Bielefelder Unternehmen Portale brauchen

Die Wirtschaftsförderung WEGE beschreibt auf ihrer Standortseite einen “starken, überwiegend inhabergeführten Mittelstand”. Im Handel nennt sie große Verbundgruppen: EK Retail, eine der größten europäischen Handelskooperationen, mit mehr als 4.200 Handelspartnern, und die KATAG AG mit rund 1.850 Standorten. Dazu kommen Großhändler wie Kahmann & Ellerbrock im technischen Großhandel. Im Maschinenbau arbeiten in Bielefeld 5.483 sozialversicherungspflichtig Beschäftigte, und OstWestfalenLippe zählt nach WEGE rund 300 Maschinenbau-Unternehmen mit 40.000 Beschäftigten, überwiegend mittelständisch (Stand 30. Juni 2025).

Ich kenne die Systeme dieser Firmen nicht und schreibe nichts über sie. Mein Schluss aus diesen Zahlen ist nur ein Muster: Wo viele Handelspartner oder Servicekunden dieselben Fragen stellen, landen sie ohne Zugang am Telefon und in der Mailbox. Wo ist meine Lieferung? Schicken Sie mir die Rechnung noch einmal. Welchen Preis habe ich? Ein Portal beantwortet das aus dem ERP, ohne dass jemand im Innendienst suchen muss.

Neues Rathaus Bielefeld

Foto: Zefram, CC BY 2.0 de, Wikimedia Commons.

#Was ein Portal aus Rollen macht

Ein Portal hat selten nur eine Sorte Nutzer. Als Beispiel nehme ich einen Großhändler mit Händlerkunden:

RolleSiehtDarf
Händlereigene Aufträge, Belege, Preisliste der eigenen Kundengruppenachbestellen, Belege laden
AußendienstHändler seines GebietsAngebote anlegen
Innendienstalle AufträgeStatus ändern, Anfragen beantworten
Administratoralle BenutzerRollen vergeben, Zugänge sperren

Die Tabelle ist ein Beispiel, die Rollen Ihres Betriebs legen wir schriftlich fest, bevor ich die erste Zeile schreibe. Die LDI NRW nennt in ihrer Liste typischer technischer Maßnahmen ein “Rollen- und Berechtigungskonzept”. Eine Seite, auf der diese Rollen beschrieben sind, ist also Teil der Dokumentation, nicht nur der Technik.

#Wo die Rolle geprüft wird

Die Anmeldung selbst, also Sitzung, Cookie und Passwort-Alternativen, beschreibt die Seite zur Anmeldung in Next.js für Wiesbaden. Hier geht es um den Schritt danach: Wer angemeldet ist, darf trotzdem nicht alles sehen.

Die Datenschutz-Anleitung von Next.js empfiehlt für neue Projekte eine Datenzugriffsschicht. Sie soll nur auf dem Server laufen, Berechtigungen prüfen und kleine, sichere Datenobjekte zurückgeben. Dieselbe Anleitung rät, einen Ansatz zu wählen und nicht zu mischen, und sie sagt, dass nur die Datenzugriffsschicht auf process.env zugreifen sollte. Im Authentifizierungsleitfaden steht, der Proxy solle nicht die einzige Verteidigungslinie sein: Die meisten Prüfungen gehören so nah wie möglich an die Datenquelle.

Daraus folgt für ein Händlerportal eine einfache Regel. Die Kundennummer, nach der gefiltert wird, kommt aus der Sitzung und nie aus der Adresse. Sonst genügt es, in der URL eine andere Nummer einzutragen, um die Aufträge eines Mitbewerbers zu sehen.

Für Rollen kennt Next.js die Funktion forbidden(), die eine 403-Seite ausspielt. Die Dokumentation kennzeichnet sie als experimentell und für den Produktivbetrieb nicht empfohlen, sie braucht experimental.authInterrupts in der Konfiguration. Ich setze sie deshalb nicht ein, sondern prüfe die Rolle in der Datenzugriffsschicht und liefere bei einem Verstoß eine eigene Antwort. Wer sie dennoch nutzen will: Die Dokumentation rät, die Prüfung in die Funktion zu legen, die die Daten lädt, und diese in einer Suspense-Grenze zu rendern, damit der Rahmen der Seite sofort erscheint.

Wie eine einzelne Aktion abgesichert wird, etwa “Bestellung stornieren”, beschreibt die Seite zu Server Actions in Bonn.

#Tabellen mit serverseitiger Paginierung und Filter

Eine Auftragsliste eines Händlers mit zwei Jahren Geschäft hat tausende Zeilen. Der Browser bekommt davon nur die, die auf dem Bildschirm stehen. Next.js sieht dafür die Adresse als Zustand vor: Laut Referenz zu page.js lassen sich über searchParams Filter, Paginierung und Sortierung aus der Query-String-Adresse steuern. searchParams ist ein Promise, und es ist eine Anfragezeit-API: Wer es liest, schaltet die Seite auf Rendern zur Anfragezeit.

So sieht der Kern aus. Die Funktion parse lässt nur erlaubte Werte durch, listOrders ist die Datenzugriffsschicht (siehe unten):

// app/portal/orders/page.tsx
import { Suspense } from 'react'
import Link from 'next/link'
import { listOrders } from '@/lib/erp'

const STATUS = ['open', 'shipped'] as const

function parse(sp: { [key: string]: string | string[] | undefined }) {
  const page = Math.max(1, Number.parseInt(String(sp.page ?? '1'), 10) || 1)
  const status = STATUS.find((s) => s === sp.status)
  const search = typeof sp.q === 'string' ? sp.q.slice(0, 60) : undefined
  return { page, status, search }
}

async function Orders({ query }: { query: ReturnType<typeof parse> }) {
  const data = await listOrders(query)
  if (!data) return <p>Bitte anmelden.</p>
  return (
    <>
      <ul>{data.rows.map((o: { id: string; number: string }) => <li key={o.id}>{o.number}</li>)}</ul>
      <Link href={{ pathname: '/portal/orders', query: { ...query, page: query.page + 1 } }}>Weiter</Link>
    </>
  )
}

export default async function Page({ searchParams }: { searchParams: Promise<{ [key: string]: string | string[] | undefined }> }) {
  const query = parse(await searchParams)
  return (
    <Suspense fallback={<p>Lädt</p>}>
      <Orders query={query} />
    </Suspense>
  )
}

In meinem Test lieferte ?page=2&status=open die zweite Seite der offenen Aufträge, und ?page=abc&status=evil fiel auf Seite 1 ohne Filter zurück. Der Zustand liegt in der Adresse, deshalb funktionieren Lesezeichen, Weitergabe per Mail und die Zurück-Taste. Was in der Adresse steht, landet in Zugriffsprotokollen. Namen von Ansprechpartnern gehören deshalb nicht in einen Filter-Parameter, Auftragsnummern schon.

Wie Ihr ERP blättert, steht in dessen Schnittstellendokumentation: Seite und Größe, Versatz oder Cursor. Danach richtet sich, ob Sie direkt zu Seite 40 springen können oder nur vor und zurück. Das klären wir vor dem Bau, nicht danach.

#Belege ausliefern, die nicht öffentlich sein dürfen

Rechnungen und Lieferscheine gehören nicht in den Ordner public. Laut Dokumentation sind Dateien dort unter ihrer Adresse ab der Basis-URL erreichbar, also für jeden, der die Adresse kennt. Ein Beleg geht über einen Route Handler, der die Sitzung prüft, die Datei beim ERP holt und den Datenstrom weiterreicht. Die Referenz zu route.js zeigt, dass ein Handler eine Response mit einem Datenstrom zurückgeben kann. Grundlagen zu Route Handlern und ihrem Caching stehen auf der Seite für Wuppertal.

// app/belege/[id]/route.ts
import { getDocument } from '@/lib/erp'

export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
  const { id } = await params
  const doc = await getDocument(id) // prüft Sitzung und Kunde, wirft nichts durch
  if (!doc) return new Response('Nicht angemeldet', { status: 401 })
  return new Response(doc.body, {
    headers: {
      'Content-Type': 'application/pdf',
      'Content-Disposition': `attachment; filename*=UTF-8''${encodeURIComponent(doc.filename)}`,
      'Cache-Control': 'private, no-store',
    },
  })
}

Der Header Content-Disposition mit attachment lässt den Browser die Datei speichern statt anzeigen. MDN beschreibt filename* für Namen mit Umlauten und empfiehlt, filename und filename* für größtmögliche Kompatibilität zusammen zu senden. Mein Beispiel zeigt nur filename*. In meinem Test lieferte der Handler mit Sitzung den Datenstrom als PDF mit diesen Headern, ohne Sitzung antwortete er mit 401. Cache-Control: private, no-store setze ich, damit ein CDN oder Proxy keine Rechnung eines Kunden an den nächsten ausliefert.

#Das ERP anbinden

Ob Ihr ERP eine Schnittstelle hat, welche Version, welche Zugangsdaten und welche Grenzen, weiß ich erst nach einem Blick in dessen Dokumentation. Produktnamen nenne ich deshalb nicht vorab. Voraussetzung ist eine dokumentierte Schnittstelle, die mit Testdaten erreichbar ist.

Für die Next.js-Seite gilt Folgendes. Die Datenschutz-Anleitung nennt externe HTTP-Schnittstellen als Ansatz für bestehende Organisationen, in denen eigene Backend-Teams Schnittstellen verwalten, und rät dort zu einem Zero-Trust-Modell. Genau das ist ein ERP-Portal: Das ERP bleibt das System mit den Daten und den Regeln, Next.js bleibt die Anzeige. Aus der Anleitung zum Datenabruf ergibt sich: Ein fetch-Aufruf wird nicht zwischengespeichert und hält die Seite an, bis er fertig ist. Bei jedem Seitenaufruf geht also eine Anfrage an Ihr ERP. Deshalb bekommt jeder Abruf ein Zeitlimit, und die Last auf dem ERP gehört in die Planung.

Der Kern meiner Datenzugriffsschicht, gebaut und geprüft gegen ein Platzhalter-ERP (Pfade und Feldnamen sind dessen, Ihr ERP nennt sie anders):

// lib/erp.ts
import 'server-only'
import { verifySession } from './session'

async function erp(path: string, params: Record<string, string> = {}) {
  const url = new URL(path, process.env.ERP_API_URL)
  for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v)
  const res = await fetch(url, {
    headers: { Authorization: `Bearer ${process.env.ERP_TOKEN}` },
    signal: AbortSignal.timeout(8000),
  })
  if (!res.ok) throw new Error(`ERP antwortet mit ${res.status}`)
  return res
}

export async function listOrders(q: { page: number; status?: string; search?: string }) {
  const session = await verifySession()
  if (!session) return null
  const params: Record<string, string> = {
    customer: session.customerId, // aus der Sitzung, nie aus der URL
    page: String(q.page),
    pageSize: '25',
  }
  if (q.status) params.status = q.status
  if (q.search) params.q = q.search
  const { items, total } = await (await erp('/orders', params)).json()
  return {
    total: total as number,
    rows: items.map((o: any) => ({ id: o.id, number: o.number, date: o.date, status: o.status })),
  }
}

server-only bricht den Build ab, falls jemand diese Datei aus einer Client-Komponente importiert. Der Zugriffstoken steht in einer Umgebungsvariable und ist nur hier lesbar. Die Zeilen der Tabelle werden auf vier Felder gekürzt: Das ERP liefert oft mehr, als ein Händler sehen darf, und die Dokumentation rät, nur das zurückzugeben, was gebraucht wird. Wie ein langsames ERP die Seite nicht blockiert, beschreibt die Seite zu Datenabruf und Streaming in Karlsruhe.

#Datenschutz in einem Portal mit Firmenkunden

Ein Händlerportal verarbeitet personenbezogene Daten der Ansprechpartner Ihrer Kunden: Name, Mailadresse, Bestellverhalten. Die zuständige Aufsicht in Nordrhein-Westfalen ist die LDI NRW. Auf ihrer Seite zu den technischen und organisatorischen Maßnahmen nennt sie als typische technische Maßnahmen unter anderem ein Rollen- und Berechtigungskonzept, ein Löschkonzept und sichere Authentifizierungsverfahren. Die DS-GVO sieht eine Dokumentation aller Maßnahmen vor, sie dient als Nachweis, dass angemessene Maßnahmen umgesetzt sind.

Zwei weitere Seiten der LDI NRW betreffen ein Portalprojekt. Das Verarbeitungsverzeichnis soll festhalten, ob Daten an Externe übermittelt werden. Die Seite zur Auftragsverarbeitung verlangt einen Vertrag, wenn ein Dienstleister im Auftrag Daten verarbeitet, und der Auftragsverarbeiter soll seine Eignung vor Vertragsabschluss belegen können. Je nach Aufbau kann das Ihren Hoster betreffen und jeden Dienst, der Mails oder Protokolle verarbeitet. Ich liefere Ihnen eine Beschreibung der Datenflüsse und der Rollen, mit der Sie Ihr Verzeichnis ergänzen. Die rechtliche Bewertung bleibt bei Ihrem Datenschutzbeauftragten.

#Checkliste vor dem Projekt

FrageWarum
Hat das ERP eine dokumentierte Schnittstelle mit Testdaten?Ohne sie baue ich nur Attrappen
Welche Rollen gibt es, und wer vergibt sie?Die Rollen bestimmen die Datenzugriffsschicht
Woher kommt die Kundennummer einer Sitzung?Aus der Sitzung, nie aus der Adresse
Wie blättert die Schnittstelle?Seitenzahl, Versatz oder Cursor ändern die Tabelle
Welche Filter braucht der Innendienst wirklich?Jeder Filter ist eine Abfrage an das ERP
Welche Belege gibt es, und wo liegen sie?Sie dürfen nicht in public liegen
Wer hostet, und gibt es einen Vertrag zur Auftragsverarbeitung?Pflicht nach der DS-GVO bei einem Dienstleister
Gibt es ein Löschkonzept für Konten und Protokolle?Teil der Maßnahmen, die die LDI NRW nennt

#So läuft ein Projekt mit mir

Ich arbeite aus der Ferne und schriftlich, ohne Termine und Telefonate. Zum Start genügt eine Mail mit der Adresse Ihres ERP-Anbieters oder dessen Schnittstellendokumentation, Ihrem Ziel und Ihrer Frist. Danach lese ich die Dokumentation und stelle meine Fragen schriftlich. Sie erhalten schriftlich den Umfang samt Annahmen. Ich baue auf einem Staging-System, und live geht erst etwas nach Ihrer schriftlichen Freigabe. Layout, Mockups und Wireframes bringen Sie mit, am einfachsten als Tabelle, in der Sie pro Ansicht markieren, welche Elemente wohin gehören. Ich setze sie um.

Zum Start brauche ich Zugang zu einer Testumgebung Ihres ERP, die Liste der Rollen und eine Aufstellung der Ansichten. Läuft das Portal später weiter, beschreibt die Seite zur Wartung in Bielefeld die Betreuung mit getesteten Updates.

#Häufige Fragen

Wann reicht ein Shop mit B2B-Funktionen, und wann lohnt ein eigenes Portal? Wenn Händler Waren aus einem Katalog bestellen und die Preise je Kundengruppe feststehen, kann ein Shopsystem genügen. Ein eigenes Portal mit Next.js lohnt, wenn Aufträge, Belege oder Lagerstände aus Ihrem ERP kommen und dort bleiben sollen. Die Seite zum B2B-Shop mit Shopify in Bielefeld beschreibt die andere Seite dieser Entscheidung.

Wo prüfe ich die Rolle eines Nutzers? In der Datenzugriffsschicht, also dort, wo die Daten geholt werden. Die Next.js-Dokumentation nennt für neue Projekte genau diese Schicht und warnt, dass der Proxy nicht die einzige Verteidigungslinie sein soll. Ein Menüpunkt, den ein Händler nicht sieht, schützt keine Daten.

Warum liegen Seite und Filter einer Tabelle in der Adresse? Die Seite liest searchParams auf dem Server und fragt nur die 25 Zeilen ab, die sie zeigt. Die Adresse lässt sich als Lesezeichen speichern oder in einer Mail weitergeben, und die Zurück-Taste funktioniert. Welche Werte erlaubt sind, legt eine Liste im Code fest.

Wie liefere ich Belege aus, die nicht öffentlich sein dürfen? Über einen Route Handler, der Anmeldung und Zugehörigkeit prüft und die Datei durchreicht. Liegt eine Datei im Ordner public, ist sie laut Next.js-Dokumentation für jeden unter ihrer Adresse erreichbar. Das passt für Bilder, nicht für Rechnungen.

Was passiert, wenn das ERP langsam antwortet oder nicht erreichbar ist? Jeder Abruf hat ein Zeitlimit, und die Tabelle steht in einer Suspense-Grenze, damit der Rahmen der Seite sofort erscheint. Der Händler sieht dann eine Fehlermeldung statt einer leeren Seite. Wie Sie langsame Systeme im Datenabruf entschärfen, beschreibt die Seite für Karlsruhe.

Gestalten Sie die Oberfläche des Portals? Nein. Die Vorlagen liefern Sie oder Ihre Agentur, als Mockups oder Wireframes. Ich setze sie um und baue die Anbindung an Ihr ERP.

#Ihr Portal in einer E-Mail

Wenn Sie wissen wollen, ob ein Portal für Ihre Händler oder Kunden mit Next.js und Ihrem ERP machbar ist, schreiben Sie mir über das Kontaktformular. Schicken Sie mir den Namen Ihres ERP, die Rollen, die Sie sich vorstellen, Ihr Ziel und den Wunschtermin. Ich antworte werktags schriftlich, zunächst mit Rückfragen, und die erste schriftliche Schätzung kostet nichts.

Brauchen Sie die Leistung: Next.js-Entwickler in Bielefeld?

Beschreiben Sie Ihr Projekt per E-Mail. Antwort innerhalb von 48 Stunden.

Projekt beschreiben

Weitere WordPress-Dienste und Wissensbasis entdecken