In Notion schreiben und automatisch mit einem statischen Blog synchronisieren
Wenn du gern in Notion schreibst, deine Website aber mit Astro oder einem anderen file-first Static Stack gebaut ist, entsteht schnell eine kleine Workflow-Lücke, die stärker nervt, als sie sollte.
In Notion zu entwerfen fühlt sich angenehm an. Abschnitte lassen sich leicht verschieben, Bilder ebenso, und die Seite bleibt lesbar, während der Text noch Form annimmt. Sobald es ans Publizieren geht, bist du plötzlich wieder im Repository: Inhalte kopieren, Frontmatter korrigieren, eine englische Version erzeugen, Build starten, Änderungen pushen und auf das Deployment warten. Die erste Hälfte fühlt sich nach Schreiben an. Die zweite nach Wartungsarbeit.
Genau diese Lücke wollte ich entfernen. Notion sollte meine Schreiboberfläche bleiben, aber der Astro-Blog sollte nicht wieder zu einem backendlastigen Content-System werden. Daraus entstand eine bewusst schmale Pipeline: Artikel in Notion schreiben, GitHub Actions die Seite abrufen lassen, daraus Markdown-Dateien im Repository erzeugen, englische Version und Slug generieren, Build validieren und anschließend in den bestehenden Produktions-Deploy-Flow weiterlaufen.
Wenn dein Blog noch nicht auf einem file-first Setup basiert, ist Astro vs WordPress: Warum ich Markdown und GitHub Actions für einen Entwickler-Blog bevorzuge der bessere Einstieg. Wenn du bereits direkt aus Markdown im Repository veröffentlichst, behandelt Markdown lokal schreiben, mit git push deployen: mein zweisprachiger Blog-Publishing-Workflow diese Seite. Dieser Beitrag liegt dazwischen: den Schreibkomfort von Notion behalten, ohne Struktur und Release-Disziplin eines Astro-Content-Repositories aufzugeben.
Mein erster Impuls war derselbe wie bei vielen anderen: sofort event-driven bauen. In Notion veröffentlichen, Webhook auslösen, Worker triggern, Dateien schreiben, Produktion deployen. Auf dem Papier klingt das sauberer. Je genauer ich reale Fehlerpfade abgebildet habe, desto mehr bin ich davon abgerückt. Für einen persönlichen Blog ist in Version eins nicht Geschwindigkeit am wertvollsten, sondern Debugbarkeit. Deshalb blieb die erste Version absichtlich schlicht. Der Artikeltext liegt im Notion-Seiteninhalt, Properties enthalten nur Metadaten wie Titel, Summary, Kategorie, Tags, Slug und Status. Jeder Lauf verarbeitet genau eine Seite. Die Standard-Queue-Regel lautet publish = true plus status = Ready, und ein bestehender Beitrag darf nur aktualisiert werden, wenn der ursprüngliche Slug wiederverwendet und Updates ausdrücklich erlaubt werden.
Das wirkt nicht spektakulär, macht die Pipeline aber leicht nachvollziehbar. Kommt eine Seite nie in die Queue, stimmt wahrscheinlich das Metadata-Setup nicht. Schlägt die englische Generierung fehl, ist das eine eigene Schicht. Scheitert der Build, geht es zurück zum Content Schema. Läuft das Deployment nie, schaut man in den nächsten Workflow. Mir ist eine langsamere erste Version, die sich offensichtlich debuggen lässt, lieber als ein „Echtzeit“-Setup, das beim ersten Fehler unangenehm wird.
Für einen Solo-Blog ist dieser Trade-off besonders sinnvoll. Ein Team kann Instant Publishing, Multi-Editor-Review-States und reichere Workflow-Events brauchen. Meine Frage ist einfacher: Wenn ich abends einen Beitrag fertigstelle, kann er am nächsten Morgen auf der Website stehen, ohne dass ich versehentlich etwas überschreibe? Polling ist nicht glamourös, für diesen Job aber erstaunlich schwer abzulehnen.
Entfernt man die Produktnamen, ist der Workflow simpel:
Notion page body
-> mark page as Ready + publish
-> GitHub Actions pulls markdown
-> generate English title, description, body, and slug
-> write files into src/content/posts/zh-cn and en-us
-> run build validation
-> push to main
-> trigger the production deploy workflow
-> write status, links, and commit SHA back to Notion
Zwei Dinge waren mir dabei besonders wichtig. Erstens ist Notion nur die Schreiboberfläche, nicht das finale Veröffentlichungsartefakt. Die Live-Seite entsteht weiterhin aus Markdown-Dateien im Repository. Zweitens darf Automation nicht den Schutz vor versehentlichem Überschreiben kosten. Deshalb bleibt die Content-Struktur im Repository bewusst einfach:
src/content/posts/
├── zh-cn/
│ └── notion-to-astro-auto-publish-workflow.md
└── en-us/
└── notion-to-astro-auto-publish-workflow.md
Beide Sprachversionen verwenden denselben path, wodurch Astro-Routing und Aggregation sauber bleiben. Auch auf der Notion-Seite sind die Pflichtfelder begrenzt: Titel, Summary, Kategorie, Tags, Slug, Publish-State, Queue-State, Auto-Translate und das explizite Update-Flag. Der Body bleibt im Seiteninhalt und nicht in einer Property. Das klingt selbstverständlich, ist aber wichtig. Das Skript zieht exportiertes Markdown aus der Seite. Es baut den Artikel nicht neu auf, indem es Block für Block durchläuft und errät, wie er serialisiert werden soll. Solange Notion die Seite sauber exportieren kann, lässt sie sich ohne große Zusatzlogik in eine normale Content-Datei umwandeln.
Wenn die praktischere Frage lautet, wie man in Notion schreiben sollte, damit der Artikel sauber in einen statischen Blog synchronisiert wird, ist meine Antwort inzwischen einfach: Notion wie einen komfortablen Markdown-Editor behandeln, nicht wie eine visuelle Layout-Spielwiese. Also normale Heading-Level, normale Absätze, fenced Code Blocks für Befehle und Konfiguration, Bilder nahe am zugehörigen Absatz und eigene Zeilen für Links oder Videos, die später eventuell erweitert werden. Je stärker man von Notion-spezifischen Präsentationstricks wie großen Bookmark-Karten, Multi-Column-Layouts oder dekorativen Blöcken abhängt, desto mehr Aufräumarbeit entsteht nach dem Export.
Am besten funktioniert für mich eine sehr schlichte Form. Mit einem Problem beginnen, die Hauptargumentation in einigen längeren Absätzen tragen, Code Blocks dort einfügen, wo Befehle oder Config wirklich wichtig sind, Bilder nahe am passenden Absatz lassen und wichtige Referenzlinks oder Videos auf eine eigene Zeile setzen. Dann verhält sich die Notion-Seite bereits fast wie eine saubere Markdown-Quelldatei – genau deshalb wird die Synchronisierung zuverlässig.
Das konservativste Muster sieht ungefähr so aus:
# Title
Open with two or three paragraphs that explain the problem and the decision.
## One key section
Keep writing.
```bash
pnpm build
node scripts/notion-auto-publish.mjs process
```
Then add pitfalls, limits, or recovery notes.
https://example.com/some-reference
Wer so schreibt, erlebt den Sync nicht mehr als „schicke Notion-Seite in einen Blog umwandeln“, sondern eher als „einen sauberen Source-Draft aus Notion ins Repository legen“. Dieser Perspektivwechsel macht viel aus.
Drei Commands, drei Aufgaben
Auch die Script-Entrypoints bleiben eng:
node scripts/notion-auto-publish.mjs process
node scripts/notion-auto-publish.mjs finalize-success
node scripts/notion-auto-publish.mjs finalize-failure
process erledigt die eigentliche Arbeit. Es wählt eine zulässige Seite, holt den Body, generiert die englische Seite, schreibt beide Markdown-Dateien und lässt anschließend Build- und Release-Kette weiterlaufen. Success- und Failure-Callbacks kümmern sich nur um den Write-back zu Notion. Diese Trennung hält jede Phase ehrlich. Das Skript wird nicht zu einem übergroßen Block, der gleichzeitig Notion abfragt, Inhalte übersetzt, Build-Output validiert, Git pusht und Statuswerte patcht.
Je länger ich dieses Setup nutzte, desto weniger interessierte mich der Übersetzungsschritt an sich und desto wichtiger wurde Slug-Schutz. Sobald Publishing automatisiert ist, lautet das größte Risiko meistens nicht „ich habe einen zusätzlichen Button geklickt“, sondern „ich dachte, ich veröffentliche einen neuen Beitrag, aber das System glaubte, ich wolle einen alten ersetzen“. Ähnliche Titel und automatisch generierte Slugs machen dieses Risiko real.
Deshalb ist die Regel absichtlich streng. Neue Posts sollten möglichst einen eindeutigen Slug bekommen. Existiert der Slug bereits im Repository, schlägt der Prozess standardmäßig fehl. Ersetzen darf er nur, wenn Update-Berechtigung ausdrücklich aktiviert wurde. Die Pipeline ist bewusst konservativ: lieber stoppen und beschweren, statt still zu raten, dass ein Überschreiben gemeint war. Einen fehlgeschlagenen Publish nachzuholen ist leicht. Eine versehentliche Ersetzung zu reparieren ist meist unangenehmer.
Geprägt wurde das Setup auch durch das Verhalten OpenAI-kompatibler Gateways. Ich habe bereits merkwürdige Fälle gesehen: 200 OK ohne brauchbaren Body, eine leere Non-Streaming-Antwort während Streaming funktionierte oder akzeptierte Schemas mit falschen Feldern. Deshalb besitzt die Translation Layer mehrere Fallbacks. Nicht weil ich ein cleveres Skript wollte, sondern weil englische Generierung und Slug-Generierung innerhalb eines Publishing-Pfads nicht fragil sein dürfen. Das Modell ist kein magischer Publisher, sondern nur ein austauschbarer Transformationsschritt. Es kann bei englischem Draft, Titel und Description helfen; die harten Grenzen gehören weiterhin ins Repository: Content Schema, Build-Validierung und Overwrite Protection.
Die entscheidenden Grenzen
Nach realer Nutzung glaube ich nicht, dass die gefährlichen Stellen im Artikeltext liegen. Sie liegen an den Übergängen. Du glaubst, einen alten Beitrag zu aktualisieren, änderst aber den Slug. Du denkst, Properties enthalten den Body, obwohl das Skript sie nie dafür liest. Du interpretierst Published als vollständig verifiziertes Deployment, während es in dieser Version nur bedeutet, dass die Content-Pipeline den Production-Deploy-Trigger erfolgreich erreicht hat. Oder du nimmst an, Automation ersetze manuelle Tests, obwohl Build-Fehler weiterhin im Repository debuggt werden müssen.
Auch ein anderes Missverständnis ist häufig: „In Notion schreiben und automatisch publizieren“ klingt so, als könne man die Seite beliebig gestalten und die Pipeline würde daraus magisch einen sauberen Beitrag erzeugen. So sehe ich es nicht mehr. Notion ist in diesem Setup ein komfortableres Markdown-Authoring-Interface. Seit ich es so behandle, ist der Workflow besser. Weniger wichtig ist, ob jeder Block in Notion schön aussieht. Wichtiger sind klare Heading-Struktur, korrekte Code Fences, Bilder an der richtigen Stelle und wichtige Links auf eigenen Zeilen.
Darum mag ich weiterhin einen konservativen Debug-Pfad. Für einen Seitentest starte ich den Workflow manuell, übergebe eine konkrete page_id und lasse zuerst dry_run aktiv. Die Notion-Seite wird trotzdem geladen, die englische Version erzeugt, temporärer Output geschrieben und der Build validiert, aber nichts wird gepusht oder in Produktion geändert. Automation lässt sich leichter vertrauen, wenn man sie bereits durch die riskanten mittleren Stufen beobachtet hat.
Auf wenige Arbeitsregeln reduziert: Body in den Notion-Seiteninhalt schreiben, nicht in Properties; neuen Posts möglichst einen eigenen Slug geben; bei Updates den ursprünglichen Slug wiederverwenden und Updates ausdrücklich erlauben; zunächst die chinesische Quelle publishable machen und erst danach Englisch automatisiert erzeugen; und bei einem fehlgeschlagenen Run nicht auf das finale exit 1 starren, sondern den ersten tatsächlich fehlgeschlagenen Schritt finden.
Genau deshalb habe ich mich für dieses Design entschieden. Es ist keine besonders ambitionierte Blog-Architektur. Es zieht nur eine klare Linie zwischen drei Dingen, die ich behalten wollte: Notion als Schreiboberfläche, Astro als file-first Publishing-Ziel und GitHub Actions als Schicht, die die repetitive Arbeit dazwischen übernimmt.
Für mich ist das deutlich nützlicher als noch ein zusätzlicher Admin-Button.