Ich habe meinen letzten n8n-Workflow gelöscht. Convex erledigte den 30-Minuten-Job in 80 Zeilen TypeScript.

9 min read

Diese Woche habe ich meinen letzten n8n-Workflow gelöscht. Er lief 8 Monate lang ohne zu murren. Gelöscht habe ich ihn, weil mir eines Abends beim Betrachten meines Stacks etwas Dummes auffiel: Convex, Infisical, NetBird, Traefik – alle über dasselbe VPN-Mesh verbunden, alle versioniert im selben Monorepo, alle code-reviewbar.

Kurz gesagt: Visual Editors hatten ein echtes Killer-Feature: „Kein Code nötig." Claude Code hat dieses Feature gerade getötet. Die Frage ist jetzt, ob dein Job tatsächlich die visuelle Ebene verlassen muss, oder ob er dort bestens aufgehoben ist.

Alles außer einem Service. n8n. Seine Source of Truth war eine lokale SQLite plus 3 Google Sheets, die ich nicht mehr zu schließen wagte.

Ich habe nichts gegen n8n. Ich habe es 2 Jahre lang geliebt und an seinen esoterischen JS-Expressions geblutet (jeder, der mal {{$node["Webhook"].json["body"]["data"][0]["nested"]}} probiert hat, weiß Bescheid). Aber das hier ist einfach Rationalität. Aufgewendete Zeit, erbrachte Leistung, verkaufte Deliverables.

Zwei Büroangestellte an Schreibtischen: einer überfordert von verworrenen Workflow-Automatisierungs-Knoten und verstreuten JSON-Ausdrucken, der andere programmiert ruhig sauberen TypeScript-Code, während ein kleiner Roboter-Hummer über den chaotischen Schreibtisch surft.
n8n-Chaos vs. TypeScript-Zen: 2.633 Zeilen treffen auf 80 Zeilen.

Ich konnte keinen git diff auf meinen eigenen Workflow machen

Mir fiel auf, dass es der einzige Service in meinem Stack war, auf den ich kein git diff machen konnte. Nicht testbar. Nicht einfach wiederholbar. Das Einzige, was mir entglitt.

Und das Schlimmste daran ist, dass dieser spezifische Workflow beim Export 2.633 Zeilen JSON wog. 2.633 Zeilen, die niemand, ich eingeschlossen, ernsthaft code-reviewen konnte. Ich habe es einmal versucht, nachdem ein Junior-Kollege mich fragte, was ein bestimmter Node macht, und mir auffiel, dass ich es vergessen hatte. Ich öffnete den Export in VS Code, scrollte 4 Minuten lang, gab auf und ging zurück zum visuellen Editor, um den Node durch Herumklicken zu finden. Das war der Moment, in dem ich es wusste. Ein Stück Infrastruktur, das du nur durch Anklicken auditieren kannst, ist keine Infrastruktur. Es ist ein Tamagotchi.

n8n ist nicht schlecht designed. Es ist für einen anderen Job designed. Der Mismatch liegt bei mir, nicht bei n8n, und ich hatte ihn 8 Monate lang mitgeschleppt, ohne ihn zu benennen.

Die 2.633 Zeilen JSON, die ich nicht mal code-reviewen konnte

TITLE "The 2,633-Line Tax" + subtitle "what versioning costs when your workflow lives in JSON". Metaphor: a giant scroll of JSON unrolling from a printer, with a tiny human at the bottom trying to read it through a magnifying glass, while a green git terminal on the left shows clean diffs and a red JSON terminal on the right shows a wall of unreadable characters. Style: Franco-Belgian ligne claire comic, thick black outlines, flat shading, expressive faces. Palette: paper cream #FFF8E7, ink black #111111, hot red #E63946, terminal green #4CAF50, sky blue #4FC3F7. Content: left panel labeled "CODE" with a clean git diff (+12 -3), right panel labeled "JSON EXPORT" with a chaotic wall of curly braces and quotation marks, the human in the middle holds a sign reading "I am supposed to review this". Highlight: the JSON wall has a glowing red 2633 number in the corner, the git diff has a small green checkmark. Legend: bottom-left sticky note, "left = reviewable / right = vibes-based". Footer: copyright rentierdigital.xyz. NOT flat corporate vector, NOT minimalist tech aesthetic.
Die versteckten Kosten JSON-basierter Development-Workflows

30+ Nodes. Google Sheets als Datenbank verwendet, mit leeren Spalten als State Machine, weil das machst du halt, wenn dein Tool dir keine echte State Machine gibt. Eine Polling-Schleife gegen eine externe LLM-API, die zwischen 90 Sekunden und 30 Minuten pro Call braucht und ihren Platz verliert, sobald n8n neustartet. Webhook-Receiver, die zweimal feuern, wenn n8n unter Last steht. Ein Queue-Mode-Setup, für das ich ein Wochenende getuned habe.

Ich hacke hier nicht nur auf n8n herum. Make ist dasselbe Problem mit schlechterem Pricing. Pay-per-Operation, keine Versionskontrolle, Friction sobald du etwas Ernsthaftes brauchst. Zapier ist wieder dasselbe, abgerechnet pro Task, jedes IF/ELSE kostet dich. n8n lässt dich wenigstens self-hosten. Das ist der einzige architektonische Gewinn gegenüber den anderen beiden, und selbst dieser Gewinn kommt mit einem Queue-Mode-Setup, das ein Wochenende zum Tunen braucht.

Wenn ich sage „Ich kann keine 2.633 Zeilen JSON code-reviewen", bin ich noch großzügig. Das n8n-Team gibt es selbst zu. In ihrem eigenen offiziellen Forum, in einem Thread mit dem Titel N8n performance and scalability, schreiben sie: „Scaling n8n is currently not that easy" und „n8n does not scale properly yet." Und ein Operator in einem Community-Forum berichtete, dass sein self-hosted n8n zuverlässig unter Last-Spitzen von 2.000 Requests in wenigen Minuten crasht. Zuverlässig. Sprich, du kannst damit planen.

Das ist kein Hater auf Reddit. Das ist n8n über n8n.

Das 80-Zeilen-TypeScript-Pattern, das alles ersetzte

Hier ist das Pattern, das die 30+ Nodes ersetzte. Self-rescheduling Polling mit Convex's Scheduler. State wird bei jedem Schritt persistiert. Wenn der Worker zwischen zwei Polls crasht, nimmt der Scheduler genau dort wieder auf, wo er aufgehört hat. Kein Queue-Mode. Keine SQLite zum Backup.

import { internalAction, internalMutation } from "./_generated/server"
import { internal } from "./_generated/api"
import { v } from "convex/values"

// Kick off a long-running external job
export const startJob = internalAction({
  args: { jobId: v.string(), payload: v.any() },
  handler: async (ctx, { jobId, payload }) => {
    const remoteId = await callExternalLLM(payload)
    await ctx.runMutation(internal.jobs.recordStart, { jobId, remoteId })
    // Schedule the first poll in 30 seconds
    await ctx.scheduler.runAfter(
      30_000,
      internal.jobs.pollStatus,
      { jobId, remoteId, attempt: 1 }
    )
  },
})

export const pollStatus = internalAction({
  args: {
    jobId: v.string(),
    remoteId: v.string(),
    attempt: v.number(),
  },
  handler: async (ctx, { jobId, remoteId, attempt }) => {
    // 30 min timeout (60 polls of 30s)
    if (attempt > 60) {
      await ctx.runMutation(internal.jobs.markTimeout, { jobId })
      await ctx.runAction(internal.jobs.notifyIncident, { jobId })
      return
    }

    const status = await fetchRemoteStatus(remoteId)

    if (status.state === "done") {
      await ctx.runMutation(internal.jobs.recordResult, {
        jobId,
        result: status.result,
      })
      return
    }

    if (status.state === "failed") {
      await ctx.runMutation(internal.jobs.recordFailure, {
        jobId,
        error: status.error,
      })
      return
    }

    // Still running, reschedule
    await ctx.scheduler.runAfter(
      30_000,
      internal.jobs.pollStatus,
      { jobId, remoteId, attempt: attempt + 1 }
    )
  },
})

Das war's. Ungefähr 80 Zeilen, wenn du die Mutations dazuzählst, die in die Tabelle schreiben. Es macht, was 30 n8n-Nodes gemacht haben, plus ein paar Dinge, die n8n nie sauber hinbekommen hätte. Versioniert mit der App, getestet mit der App, deployed im selben CI-Pass, State lebt in derselben Convex-Tabelle, die das Frontend bereits liest, also sieht die UI die Job-Status-Updates in Echtzeit, ohne dass ich extra was machen muss.

Der Grund, warum das mit Inngest's step.sleep()-Pattern konkurriert, ist, dass jeder ctx.scheduler.runAfter()-Call die nächste Invocation dauerhaft in Convex's eigenem Store persistiert. Wenn das Deployment mid-job crasht, nimmt der Scheduler beim Hochfahren vom letzten persistierten Schritt wieder auf. Ich fake keine Durability mit einer Cron-Loop und bete.

Falls du nicht auf Convex bist, lebt dieses exakte Pattern nativ in Convex + Claude Code: The Ultimate Duo for Shipping SaaS at 3 AM. Und falls du auf Postgres bist, hast du Inngest, Trigger.dev, Temporal. Sie lösen alle dasselbe eigentliche Problem: einen long-running Job mit State, der Crashes überleben muss. Das ist das richtige Problem zum Lösen. Es ist nicht n8n's.

Die meisten Automation-Tutorials verlieren sich hier. Sie zeigen dir, wie du 12 Nodes verketten kannst, die ein LLM callen und in ein Sheet schreiben, aber sie zeigen dir nie, was passiert, wenn der LLM-Call 14 Minuten dauert und dein n8n-Container bei Minute 9 OOMt. Die Antwort ist: Du machst den Job von vorne und heulst. Das Pattern oben macht die letzten 30 Sekunden nochmal und geht weiter.

Die 4-Fragen-Architektur-Map: Wie ich tatsächlich wähle

TITLE "The 4-Question Map" + subtitle "duration on Y, code coupling on X". Metaphor: a 2x2 architecture map drawn like an old subway map, 4 stations connected by colored lines, each station representing a tool family. Style: engineer blueprint, white lines and text on a deep navy background, tech-pen feel, drafting compass marks in the corners. Palette: blueprint navy #0B2545, chalk white #F4F4F4, neon yellow #FFD60A, hot pink #FF3E7F, mint #4ECDC4. Content: 4 quadrants labeled "SHORT + LOOSE (visual stuff: Zapier/Make/n8n)", "LONG + TIGHT (Convex/Inngest/Trigger/Temporal)", "REACTIVE CRUD (Convex direct)", "HEAVY DATA (Supabase + pg_cron / Airflow)". X axis labeled "code coupling: loose to tight", Y axis labeled "duration: seconds to hours". Highlight: the "LONG + TIGHT" quadrant glows in neon yellow with a small lightning bolt, indicating where the article focuses. Legend: bottom-right corner, "yellow station = where the article lives". Footer: copyright rentierdigital.xyz. NOT flat corporate vector, NOT minimalist startup aesthetic.
Die 4-Fragen-Architektur-Map für Tool-Auswahl

Ich brauchte eine Entscheidungsregel, die die nächsten 4 Framework-Launches überlebt. Hier sind die 4 Fragen, die ich jetzt stelle.

Frage 1. Job unter 30 Sekunden, Glue-Work zwischen SaaS-Tools, keine Business-Logic?
Nimm das visuelle Zeug. Zapier, Make, n8n, alles vertretbar. Die Kosten des visuellen Editors sind real, aber der Upside (du onboardest einen Non-Dev-Kollegen an einem Nachmittag) ist auch real. Code zu wählen wäre hier Overengineering.

Frage 2. Job zwischen 5 und 30 Minuten, durable State, Retries, Resume nach Crash?
Code oder du stirbst. Convex Scheduler, wenn du bereits auf Convex bist. Inngest, Trigger.dev oder Temporal andernfalls. Niemals visueller Editor. Ein Trigger.dev-Kunden-Testimonial auf ihrer Landing Page sagt es unverblümt über Zapier und n8n: „they become complex, really slow, expensive and time-consuming to manage for large automations." Das deckt sich exakt mit meiner eigenen Erfahrung.

Frage 3. CRUD plus Real-time plus reactive Frontend?
Convex direkt. Füg kein Inngest obendrauf hinzu. Den Orchestration-Layer zu verdoppeln bedeutet, den State zu verdoppeln, die Failure-Modi zu verdoppeln, den Auth-Dance zu verdoppeln. Convex's Scheduler reicht für 90% der Background-Jobs, die eine SaaS-App braucht. Ich denke, das ist die Linie, bei der ich am wenigsten sicher bin, ehrlich gesagt. Wenn deine Real-time-Needs in Multi-Region mit strikten Ordering-Garantien wachsen, ist Convex vielleicht nicht die Antwort in 18 Monaten und du willst eine richtige Queue. Für jetzt hält es.

Frage 4. Heavy Data Pipeline, komplexes SQL, DAGs?
Supabase plus pg_cron, oder Airflow. Convex macht kein ad-hoc analytical SQL. Versuch es nicht. Hier hört Convex auf, das richtige Tool zu sein, und Postgres-förmige Infrastruktur fängt an zu gewinnen.

Das ist der gesamte Entscheidungsbaum. Die 2 Achsen (Duration und Coupling) liegen darunter, aber du musst nicht jedes Mal darüber nachdenken. Du fragst einfach, wie lange der Job lebt und wie eng er den Rest deines Codes berührt, und das Tool fällt aus der Antwort heraus.

Der Grund, warum ich diesem Framework mehr vertraue als Tool-by-Tool-Vergleichen, ist, dass es 3 Stack-Migrationen in 2 Jahren überlebt hat. Ich bin von Supabase zu Convex gewechselt, von Vercel Cron zu Convex Scheduler, von n8n zu TypeScript, und die 4 Fragen haben sich bei keinem dieser Moves auch nur einmal geändert. Nur die Namen der Tools. Der echte Test jeder Architektur-Entscheidungsregel ist meiner Erfahrung nach, ob sie 18 Monate später noch Sinn macht, wenn die Hälfte der Produkte auf der Folie eine neue Pricing-Page, ein neues Logo oder einen neuen Käufer hat. Wenn sie noch Sinn macht, behalte sie. Wenn nicht, war es Tool-Shilling verkleidet als Framework, und du kannst es mit der Slide-Deck wegwerfen.

Wofür ich n8n immer noch verwenden würde

Ich führe keinen Kreuzzug. Es gibt Jobs, bei denen n8n immer noch gewinnt, und Convex wäre offensichtlich die falsche Antwort.

Ein Google Sheet mit einem Slack-Channel syncen, wenn sich eine Zelle ändert. E-Mail-Benachrichtigung, wenn eine neue PostgreSQL-Zeile erscheint. Stripe-Webhook an Discord weiterleiten. Jeder stabile, repetitive Job ohne Business-Logic unter 30 Sekunden. Für all diese schlägt n8n das Schreiben von TypeScript. Nicht weil n8n großartig ist. Weil der Job klein genug ist, dass der Overhead des visuellen Editors kleiner ist als der Code-Overhead. Der Break-even-Point ist real und n8n sitzt bequem auf der richtigen Seite davon für diese Klasse von Work.

Ich habe vor ein paar Wochen einen ganzen Artikel darüber geschrieben, warum ein Open-Source-Repo Claude Code in einen n8n-Architekten verwandelte. Dieser Artikel und dieser hier widersprechen sich nicht. Claude Code macht n8n viel besser bei den Dingen, bei denen n8n bereits gut ist. Dieser Artikel handelt von dem Moment, in dem ein Job aufhört, eines dieser Dinge zu sein. Der Moment, in dem dein Workflow durable State, Retries, Replay, Code Review braucht, der Moment, in dem ein Junior-Kollege auf den JSON-Export schaut und fragt „ist das normal?" Das ist, wenn du gehst.

(Mein Pool-Typ kam gestern Morgen vorbei, während ich über all das nachdachte, fragte mich, was ich baue, und ich sagte „ein Ding, das ein anderes Ding 30 Minuten lang beobachtet." Er nickte langsam und sagte „ok" auf die Art, wie man „ok" zu jemandem sagt, der offensichtlich nicht genug geschlafen hat. Er hat nicht unrecht. Die Kinder kamen eine Stunde später aus der Schule zurück, der Hund klaute eine Tortilla von der Theke, und ich vergaß Convex für den Rest des Nachmittags.) 🐕

Wähle Orchestration wie Infrastruktur, nicht wie ein Tool

Ein Orchestration-Layer zu wählen ist keine Tool-Wahl. Es ist eine Architektur-Wahl. Tools bewegen sich alle 18 Monate. Convex wird einen Konkurrenten bekommen. Inngest wird ein neues Pricing-Tier bekommen. n8n wird eine v3 shippen. Das Framework darunter (wie lange lebt der Job und wie eng berührt er den Rest deines Codes) bewegt sich nicht. Es war vor n8n wahr. Es wird nach n8n wahr sein.

Und falls du das hier liest und denkst, dein aktueller n8n-Workflow sollte zu Code wechseln, vielleicht. Vielleicht auch nicht. Lass die 4 Fragen laufen. Wenn die Antwort „Glue-Work zwischen SaaS, unter 30 Sekunden, keine Business-Logic" ist, bleib bei n8n. Es ist buchstäblich dafür gebaut.

Lösche diese Woche einen Workflow. Den kleinsten, den du hast. Schau, wie es sich anfühlt.

Quellen

Dieser Post könnte Affiliate-Links enthalten. Wenn du sie anklickst, verdiene ich möglicherweise eine kleine Provision, kostet dich nichts und hilft mir dabei, weiterhin täglich qualitativ hochwertige Artikel für dein Lesevergnügen zu liefern.