Heute ist es genau ein Monat her, dass ich in die USA gezogen bin; nachdem ich in North Carolina gelandet war und mich im Alltag eingerichtet hatte, habe ich die Gegend und die näher gelegenen Sehenswürdigkeiten im Grunde schon abgeklappert, und damit hatte ich auf einmal nichts mehr zu tun. Nur bin ich ausgerechnet kein Mensch, der Nichtstun aushält: Sobald ich Zeit habe, suche ich mir etwas zu tun das Blog wieder aufzunehmen zählt doch wohl, aber dummerweise ist die Logistik hier in den USA nicht besonders bequem, Platinen kann ich also vorerst nicht entwerfen. Dann bleibt eben nur, zurückzukommen und ein bisschen Software zu schreiben, und nebenbei die vor ein paar Monaten liegen gelassenen Baustellen wieder aufzugreifen und zuzuschütten?
Bevor ich aber anfange, über andere herzuziehen, muss ich wohl erst noch etwas Hintergrundwissen zu seam & SeamJS nachreichen (x was ist das Ding überhaupt der letzte Beitrag liegt schließlich mehrere Monate zurück, ihr habt das mit hoher Wahrscheinlichkeit längst komplett vergessen, und die README.md auf GitHub bin ich vorerst zu faul anzupassen
Rendering als Protokoll
Benutzeroberflächen sollten beschrieben, nicht ausgeführt werden.
Kurz gesagt: Seam1 ist zuerst einmal ein Protokoll und nicht noch eine SSR-Runtime. Das heißt: Zur Build-Zeit entsteht ein HTML-Skelett mit slot, und zur Laufzeit bzw. Anfragezeit werden nur noch Werte in diese slot gefüllt; if / each / match sind ebenso fest ins Skelett geschriebene Protokollknoten. Wenn also eine Anfrage eintrifft, wurde noch keine einzige UI-Komponente ausgeführt, und der Server hat nur eine einzige Aufgabe: . Damit muss er natürlich auch kein Node oder Bun mehr sein -- ein Backend in Rust / Go kann doch genauso gut ein Stück HTML befüllen, was soll daran schwer sein?! Aber ganz abgeschafft ist die JS-Runtime später auch nicht, ich meine: man kann darauf verzichten, sofern man ein paar Einschränkungen in Kauf nimmt Wie auch immer, der Leistungsgewinn insgesamt ist definitiv exponentiell, denn für die meisten SSR-Effekte muss zur Laufzeit / Anfragezeit kein UI render mehr laufen ...
Modell zur Requestzeit? 2
Etwa seit der zweiten Jahreshälfte habe ich diese Seite vollständig von TanStack Start React auf SvelteKit Svelte port. Nach der kompletten Migration stellte ich fest: Es ist wirklich gut. Der Performance-Aufwand von ssr ist um ungefähr eine Größenordnung gesunken, die Hydration kostet fast nichts, es läuft angenehm, und die Kompilier-Philosophie des Frameworks ist ebenfalls großartig und sehr leichtgewichtig. Beim Teil Kit gibt es aber immer wieder Stellen, die mich unbefriedigt zurücklassen. Das meiste davon lässt sich über Plugins lösen schließlich ist es aus Vite zusammengesetzt, doch einige Teile lassen sich einfach nicht anfassen; das liegt allerdings mit hoher Wahrscheinlichkeit an mir, denn was seam ändern will, kollidiert vermutlich grundsätzlich mit jedem existierenden meta-Framework im Kern fordere ich ihr Ausführungsmodell heraus (pro Request eine render-Funktion), ihre Datengrenze (load und UI ineinander verklebt) und ihre Backend-Fähigkeiten (fest an die JS-Runtime gebunden).
Pseudo-SSR 3
Man muss gar nicht lange überlegen: Bestimmt fragt jemand, ob das nicht einfach Astro / Qwik / Marko ist. — seam liegt in der Form der Sprache Marko am nächsten, darin, standardmäßig wenig JS auszuliefern, Astro am nächsten, und darin, nicht replay hydrate zu tun, Qwik am nächsten. Nur läuft bei ihnen request-time eben doch weiter ein UI-Programm: die jeweiligen render von island, das eine SSR von Qwik, die von Marko kompilierten JS-Templatefunktionen — sie alle führen pro Request weiterhin ein Stück renderer aus.
Bei diesen tausend {#each} setze ich im Grunde ja auch nur HTML zusammen, ich erzeuge also durchaus ein Dokument4; der Unterschied ist, dass ich ein bereits in sich abgeschlossenes Protokoll zusammensetze, statt Komponenten-Tree neu zu erzeugen. Seam will AST → IR → HTML mit slot; zur Laufzeit wird nur das Protokoll interpretiert, dazu das injection für die dynamischen Datenbereiche, und der Teil der Funktionen für UI render muss gar nicht mehr laufen. Also kann jedes beliebige Backend das befüllen, und wenn diese letzte Schicht erst einmal residual-fiziert ist, ist auch das Problem des vendor lock in JS5 gelöst. Im Grunde liegt meine Stoßrichtung mit dem oben Genannten also gar nicht auf derselben Linie (x)
Übernehmen
Seam hydratisiert, aber weder auf die klassische Art noch auf die von Qwik. Klassische Hydratation heißt: der Client render noch einmal und gleicht das Ergebnis mit DOM ab; Qwik SSR HTML plus Komponentenzustand heraus, der Client resume und replay nicht; bei Seam dagegen muss zur request-time nicht einmal der UI renderer laufen: Hydratationsgerüst, erster Bildschirm und die Daten für hydrate stammen aus ein und demselben injection. Der Client bekommt ein bereits stimmiges DOM plus dieselben Daten und hängt anhand des zur Compile-Zeit erzeugten IR Events und client state an adopt — das ist kein replay, und natürlich auch kein resume eines ganzen Komponentenbaums.
Genau deshalb ist „auf beiden Seiten je einmal rendern und dann abgleichen“ — also mismatch6 — vom Normalfall zum unzulässigen Zustand geworden. Ich behaupte nicht, die Implementierung sei zu 100 % fertig, sondern dass solche Abweichungen im Modell schlicht unzulässig sind: Tritt eine auf, ist es ein bug in meiner compiler- / injector-Implementierung und kann fix werden; typische Beispiele sind (das Gerüst lief nicht durch den HTML-Parser, das Escaping passt nicht, IR und Gerüst laufen auseinander).
Natürlich muss skeleton selbst parse-stable7 sein; wenn man in table wahllos comment hineinstopft, ist das eine schlecht gemachte Implementierung und nicht meinem Modell anzulasten. Zur Build-Zeit wird die Hydrationszuordnung berechnet (was statisch ist, was slot ist, was gebunden wird), nicht die Daten vorab festgezurrt -- festgelegt wird aber sehr wohl der Datentyp dieses slot. Die Datenwerte selbst lassen sich zur Request-Zeit weiterhin beliebig austauschen; nur müssen HTML und payload ein und dasselbe sein.
Derselbe Graph?
Astro Zwar ist auch dort der erste Bildschirm ein Dokument, und ClientRouter geht danach ebenfalls, aber der Unterschied ist: der dynamische Teil bleibt islands, jeweils einzeln render, und ClientRouter ist im Kern eher document morph als derselbe, nach der Hydration weiterlebende Komponentengraph. Der Astro-Stack taugt für so etwas also schlicht nicht; er ist nicht falsch, er ist nur nicht dafür gebaut, so benutzt zu werden. Du kannst natürlich sagen, es gebe ja die View Transitions API8, dann frage ich dich aber: kann man von der View Transitions API satt werden, kann man damit seitenübergreifend eine Motion-Animation9 drüberlegen … das führt jetzt etwas weit;
Vor der Hydration kann sowieso niemand etwas bewegen; dass es vor dem Start von JS im ersten Frame10 keine Animation gibt, ist von Haus aus niemandes Schuld; Seam garantiert auch nur, dass injection stimmt, document stimmt und der Einstieg client stimmt. Die eigentliche Bedingung greift nach der Hydration: Die Navigation von client schießt die Daten in genau den Graphen, der noch lebt (Layout bleibt bestehen), statt noch einmal über injection zu laufen und document auszutauschen. Kurz gesagt: Seam übernimmt nach der Hydration denselben statischen Komponentengraphen.
Da wäre eigentlich noch Marko 6, das meiner Vorstellung schon sehr nahekommt (Template-AST, feingranular zur Compilezeit, resume statt replay); aber gegenüber Marko lassen sich technisch vor allem zwei Linien ziehen: zum einen läuft bei request-time nach wie vor ein JS-Kompilat, die ganze Kette hängt fest an der JS-Runtime; zum anderen schreibt der Autor eben in Marko und nicht in Svelte. Ich glaube, die meisten Leute werden 2026 im Gespräch über Frontend ohnehin nur React, Vue, Svelte, Solid als einigermaßen verbreiteten UI Stack in Betracht ziehen; wenn ich es nicht sagen würde, wüssten viele vermutlich nicht einmal, was Marko überhaupt ist. Auf Svelte aufzubauen heißt für mich also nicht, „das Prinzip zu kopieren und ein Backend dranzuhängen“; ich erkläre es lieber so: Ich borge mir eine UI, die jemand geschrieben hat, die Leute benutzen, an der ständig Bugs behoben und Kanten geglättet werden -- alles Dinge, die allen vertraut sind.
Außerdem: Was das „keine Hydration“ von Qwik einspart, ist das Replay auf der Client-Seite, aber nicht das eine SSR auf dem Server11(?), also leider schade. Derzeit erfüllt kein einziges Framework meine Ansprüche, und keines gibt mir genug API, um selbst etwas hinzufügen zu können — genau deshalb habe ich vor ein paar Monaten dieses Ding namens SeamJS angefangen; und heute geht es eigentlich darum, seine roadmap neu zu planen.
Ich stimme mittlerweile voll und ganz zu: „Svelte: for building, not frameworking.12“
React
Fangen wir mit React an: Lassen wir die kommerzielle Bindung an Vercel Next.js und die Tatsache, dass es sein eigenes roadmap hat, einmal beiseite -- zuallererst fliegen einem hier die CVE nur so um die Ohren, allein das verdammt dich dazu, ständig die Social Media im Blick zu behalten, ob gerade eine neue Lücke reported & released wurde. Die Router der anderen sind im Grunde unbrauchbar, besonders Remix13, doch 2026 gibt es eine ganz besondere Option, nämlich TanStack Start. Ich nutze TanStack Router schon sehr lange und kann dazu sagen: abgesehen davon, dass Namensstil und Datenstrukturen nicht besonders hübsch geraten sind14, ist das funktional die einzige Option, die ich gelten lasse. Etwas jung ist sie schon, aber sie löst das Problem der ersten Aufrufgeschwindigkeit beim Kaltstart einer Seite, das der next-server-Prozess von Next.js 16 bis heute nicht gelöst hat. Next.js hat diese Ruckelei eigentlich schon immer auf unerklärliche Weise; es liegt nicht an zu schwacher Serverleistung, ich führe es vielmehr darauf zurück, dass entweder das Load beim Start noch nicht fertig ist oder runtime noch irgendetwas kompiliert. Und diese Struktur passt naturgemäß schlecht zu dem Prinzip von Cloudflare Worker, Instanzen an vielen Orten möglichst nah beim Nutzer zu starten; nur ein langlebiger Node-Prozess klassischer Art wie bei Vercel15 bekommt dieses Problem nicht ab(?) Das führt jetzt etwas weit; über Next.js könnte ich später einen ganzen eigenen Artikel zum Ablästern schreiben, aber TanStack16 halte ich wirklich für reif genug für den kommerziellen Einsatz, zumindest habe ich es für die Sales-Site eines Projekts eingesetzt. Aber das ist nur eine der einzigen zwei Optionen(x)
Vue
Was Vue angeht: Als ich persönlich von Vue 2 auf Vue 3 umgestiegen bin, konnte ich mich mit der Syntax und den zugrunde liegenden Mechanismen nicht mehr recht anfreunden; das Vue in meinem Kopf bleibt für immer Vue 2. Unbestreitbar bringt Vue 3 sehr viele großartige feature mit, aber verglichen mit Vue 2 fühlt sich sein Kern für mich verändert an -- und zwar sehr stark verändert. Außerdem, betriebswirtschaftlich betrachtet: Wird Vue nicht immer vollständiger an die Meta-Ebene gekettet, vom Ökosystem kontrolliert? Die Lage wirkt der von Svelte sehr ähnlich: beides Open Source, aber es ist wirklich kaum möglich, dass ein Dritter aufsteht und dir eine zweite Implementierung baut. Genau deshalb hat das Neumischen der Karten durch TanStack Start Next.js so hart getroffen -- ein Lob an Tanner Linsley. Und noch ein Punkt, ein nicht zu übersehendes Problem von Vue: In den frühen Jahren war Vue tatsächlich eine Erneuerung; aber jeder UI Stack stößt, sobald sein Ökosystem gewachsen ist, auf Ökosystemprobleme, also auf gewisse Kompromisse um der Kompatibilität willen? Besonders bei React ist dieses Problem wirklich sehr deutlich, und das heutige Vue bildet da keine Ausnahme. Wenn solche Inhalte17 auftauchen, halte ich das üblicherweise für Slop -- schlecht kann man es trotzdem nicht nennen, denn sobald Nuxt das nächste „unternehmenstaugliche" Framework wird, zählt nicht mehr zuerst, ob etwas technisch richtig oder elegant ist, sondern eine gewisse Rückwärtskompatibilität plus Stabilität (x
Evan You hat also eigentlich nichts falsch gemacht. Man könnte sogar sagen: Irgendjemand musste den geben, und er war zufällig der Geeignetste dafür, er hat also nur das Naheliegendste getan …
Solid
Das Framework SolidJS ist gut, und sein Kern ist stabil genug, aber niemand will ein Ökosystem dafür schreiben. Solid ist im Grunde das Rust früherer Jahre, nur hat Rust es durchgestanden; Rust steht in der Programmierung vor derselben Lage – wenig Ökosystem, alles muss man selbst bauen –, aber die Toolchain von Rust lässt einen das Rad reibungslos selbst bauen und die kleinste lauffähige Einheit zum Laufen bringen, so schwer ist das Radneuerfinden also gar nicht? Nur leider hat SolidJS diese Phase noch nicht überstanden und ist stattdessen in den KI-Winter geraten; ab 26 wird es besonders schwer. Der Kern von Solid 2.0 ist bereits sehr gut und sehr stabil, aber drumherum kam nichts ins Rollen. Die Idee ist völlig richtig, sie kam nur zur falschen Zeit. Und selbst die wenigen Projekte im Umfeld existieren nur in ein, zwei verstreuten Bereichen (TanStack hat etwa einen experimentellen Router für Solid) und ergeben zusammen kein vollständiges Ökosystem. Das bringt Solid heute wie künftig in eine unangenehme Lage: kein vollständiges Ökosystem – und in der heutigen Zeit, in der Agents explodieren, wird ein Agent es nicht bevorzugt wählen, solange der Schneeball nicht rollt. Und wenn nicht einmal die KI es bevorzugt, bedeutet das noch weniger Gelegenheiten, Beiträge zu bekommen, gerade in einer Zeit, in der sogar Linus Torvalds anfängt, . Ich finde es jammerschade um Solid: gute Idee, technisch richtig, elegant umgesetzt – nur wirklich zur falschen Zeit geboren …
„Vielleicht hätte ich das früher gar nicht als Problem gesehen — alles selbst zu schreiben, wäre schon in Ordnung gewesen. Aber leider sehe ich das heute nicht mehr so"
Der Ausgleichspunkt
Auf diesem Weg trifft eigentlich nur Svelte den perfekten Ausgleichspunkt. Es hat ein besseres Ökosystem als SolidJS (das bessere Geburtsjahr), und in der Performance liegt es kaum zurück -- dank seines Compilers ist es sogar oft schneller und effizienter als SolidJS?! Natürlich wird hier jemand einwenden, dass ab einer gewissen Seitenanzahl oder je mehr der Nutzer herumklickt, Sveltes Ressourcenaufwand18 gegenüber SolidJS immer weiter wächst? Dass es immer mehr Netzwerkanfragen werden? Well, theoretisch stimmt das, aber ich finde, man muss es an echten Webseiten messen: Bei 99,9 % aller persönlichen Seiten oder Content-Seiten bleiben Besucher ohnehin nicht lange, und ein wirklich langer Aufenthalt darf getrost als Ausnahmefall gelten. Zumindest auf meiner Seite gibt es derzeit kaum tiefes Weiterklicken, also stellt „viele Seiten durchblättern = Svelte lohnt sich weniger“ für mich kein Problem dar. Aber das ist nicht der Grund, warum ich Svelte gewählt habe; der Kern ist nach wie vor der Markup-AST.
Dann gibt es noch Dinge wie das Dashboard, das man Seite für Seite aufklickt -- dabei hilft diese Statistik nicht weiter, aber die Nutzer eines Dashboards sind im Vergleich zum eigentlichen Publikum der Inhalte nun mal in der Minderheit. Deshalb halte ich "sehr viele Seitenaufrufe treiben den Overhead hoch" im Kern für eine Scheinfrage; zumindest heute, vielleicht sehe ich das später anders, aber in dieser schnelllebigen Informationszeit kann ich kaum bestreiten, dass solche Nutzer äußerst selten sind; und selbst wenn jemand aus Interesse in die Tiefe geht und zum langjährigen Leser wird, findet dieses Stöbern höchstwahrscheinlich nur beim ersten Besuch statt, danach schauen sie bloß noch vorbei, um die neuen Artikel zu lesen; statistisch gesehen steht der Ertrag bei diesem Extremfall schlicht nicht im Verhältnis. Darum glaube ich lieber: in den meisten Szenarien liefert Svelte die bessere Gesamtperformance.
Gegenüber den drei oben genannten Frameworks passt Svelte nicht nur von der Ausgangslage und Positionierung her besser, es kommt dem Gefühl, blankes HTML zu schreiben, auch sehr nahe; in den frühen Tagen, als Svelte 3 sich gerade verbreitete und die Dateiendung .svelte gerade festgelegt worden war, wurde genau das sogar offiziell empfohlen, solange es das offizielle VSCode-Plugin noch nicht gab -- so sehr, dass du diesen Satz noch heute auf der Plugin-Seite von Svelte findest:
Wenn du
"files.associations": {"*.svelte": "html" }zu deinen VSCode-Einstellungen hinzugefügt hast, entferne es.
Außerdem hat mir Svelte noch viele Vorteile geboten, zunächst einmal drängt es die UI nicht in Richtung JSX + einen Haufen JS function19, und <style> ist ein Erzeugnis der Kompilierzeit statt runtime Ersteres lässt die Struktur in markup AST bleiben; Letzteres lässt CSS direkt in die IR eingehen, und Tailwind passt sich zufällig an beide Seiten an(
Blackbox
Wie schon im letzten Artikel geschrieben: Seam ist zuerst ein Protokoll, nicht noch eine SSR-Runtime. Zur Build-time entsteht HTML skeleton mit slot, zur Request-time passiert nur injection; if / each / match sind ebenfalls Protokollknoten20
Schade nur, dass ich für die Benutzeroberfläche damals React gewählt habe, um loszulegen. Dummerweise ist es in React äußerst schwierig, die Seitenstruktur zu erfassen und daraus Varianten zu erzeugen; und den Compiler umzubauen wäre kein geringerer Aufwand, als einen neuen zu schreiben …
Wenn man nichts ändert, kann man den React-Compiler weiterhin als Blackbox behandeln und tatsächlich einen Haufen render + diff ausführen, um sich die Struktur zusammenzuraten; bei endlichen Entscheidungen wie nullable / enum lässt sich das noch vollständig durchzählen, aber sobald ein Prädikat wie price > 10 auftaucht, ist der Wertebereich des Typs schlicht nicht mehr auszumultiplizieren. In V1 bleibt einem in so einem Fall nur, die Mocks von Hand anzugeben, also zum Beispiel drei Schnitte entlang price < 10 / = 10 / > 10 zu setzen;
Aber genau da liegt das Problem(?) Um diese präzisen 3 Schnitte hinzubekommen, müsstest du wissen, dass der Schnittpunkt bei 10 liegt -- doch der React-Compiler ist für mich undurchsichtig, wir wissen gar nicht, wie die Bedingung lautet. Damit besteht der Preis für TypeSafe21 darin, dass der Nutzer die Arbeit des Compilers übernimmt.
Theoretisch dürfte man das nicht den Nutzern aufbürden, sonst fliegen am Ende überall escape herum22, und CTR23 deckt weiterhin nur die paar nullable / enum ab, Strukturerkennung findet praktisch nicht statt, und wie viel Sinn das Ganze dann noch hat, weiß ich nicht mehr.
Aber diesmal geht es nicht darum, das Protokoll selbst zu ändern. Was ich ursprünglich zu skeleton, slot und injection gesagt habe, lässt sich meiner Meinung nach durchaus beibehalten, und das Backend muss weiterhin keinen UI-Code ausführen; ändern muss sich die Schicht vor dem Protokoll, nämlich die Frage, wie die Antwortseite überhaupt zu diesen Knoten kompiliert wird.
Und da komme ich nicht umhin, mich für das größte Geschenk zu bedanken, das mir Svelte gemacht hat -- markup
Weil ich die Struktur des Markups überhaupt nicht erraten muss!!!
Sichtbarer AST
Allein dadurch, dass Markup wirklich Markup ist, hat die Seitenstruktur durchaus die Chance, etwas zu werden, das ein Compiler lesen kann, statt eines Komponentenbaums, den erst ein Haufen Laufzeit-JavaScript erzeugt. Ich will nur sagen: Warum muss eine völlig normale Oberfläche unbedingt beliebig ausführbar gezeichnet werden können?Ich bestreite diese Sichtweise nicht, aber ich möchte sagen: In mindestens 97 % der Fälle braucht man beliebig ausführbares Zeichnen gar nicht,Näheres hier nachzulesen
Ein Gegenbeispiel: das, was in React
function Card({ user }) {
return user
? <div className="card">
<Avatar user={user} />
<span>{user.name}</span>
</div>
: null
}Im Kern wird immer noch eine JavaScript-Funktion ausgeführt, und man schaut, was sie zurückgibt. Selbst wenn der Compiler noch so schlau ist, bleibt das Grundmodell, mit dem er es zu tun hat: JS execution → JSX expression → element tree. In Svelte sieht das dagegen ganz anders aus.
{#if user}
<div class="card">
<Avatar {user} />
<span>{user.name}</span>
</div>
{/if}Was sie dem Compiler übergibt, ist direkt
Component
├── IfBlock
│ └── Element div
│ ├── Component Avatar
│ └── Element span
│ └── Expression user.nameFür eine gewöhnliche Kit-Anwendung ist das schon sehr gut, aber für etwas wie Seam, das server rendering in compile-time skeleton + CTR + SSR fallback zerlegen will, ist die Bedeutung wirklich weit mehr als nur ein bisschen größer.
Denn dieser Baum gab mir gleich zwei Ebenen an Information: Die erste ist Dynamik der Werte → slot, aber das war in V1 längst machbar; wichtiger ist eigentlich die zweite, denn sie gab mir Dynamik der Struktur → Kontrollfluss-Knoten auf Protokollebene. Statt die Blackbox des React-Compilers zu erraten, hat der Compiler von Svelte das richtige Frontend-Ergebnis einfach offengelegt, damit ich es lesen kann, etwa gehört {user.name} zum Ersteren, und erst {#if user} zum Letzteren.
Außerdem gelten if / each / match aus dem alten Protokoll weiterhin. Das kartesische Produkt aus dem früheren Text möchte ich aber einschränken: Es gilt nur für den Verzweigungsraum, nicht für den Werteraum. Bei endlichen Entscheidungen wie nullable / enum / bool ist die Zahl der Kombinationen endlich, und die bezahlte Aufzählung von compile-time ist mathematisch tragfähig; string / number / timestamp von JTD sind dagegen selbst nicht aufzählbar, und Zweige, die Prädikate wie price > 10, inventory < 5, items.length === 0 herausschneiden, findet man über ein kartesisches Produkt der Typwerte allein nie; setzt man in sentinel irgendeine Zahl ein, geht es immer auf dieselbe Seite.
Wenn man sie wirklich auf endliche Entscheidungen eindampfen will, muss man derive zuerst in bool / enum überführen, und diesen Schritt bekommt man von JTD nicht geschenkt (haha). V1 sound also nur bei nullable / enum, nicht für den Werteraum beliebiger Felder; das eigentliche technische Problem ist vielmehr, dass diese Blöcke von diff erraten werden und IR sich selbst nicht erklären kann.
Lowering
Doch im Zeitalter von Svelte lassen sich diese Knoten direkt aus dem AST erzeugen, und der Grund dafür ist nicht, beim Wechsel des Stacks ein paar ms zu sparen, sondern vielmehr, den Kontrollfluss von der Beobachtung24 zu lowering zu machen -- das Auffinden der Struktur übernimmt der AST, begrenzte Entscheidungen laufen über Protokollknoten, und N Ausfertigungen von HTML müssen nicht mehr auskombiniert werden; Typen sollen payload einschränken, nicht den Baum entdecken. Am Ende sehen die values in Svelte auf diesem Layer also so aus wie unten ↓
STATIC
<div class="card">
DYNAMIC
{user.name}
STATIC
</div>Solches DYNAMIC braucht in vielen Fällen gar nicht das Niveau von React, also「echtes Rendering25」; der Slot allein genügt.
Noch feingliedriger: Svelte kann ein einzelnes Element sogar so weit aufteilen
structure: static
attributes: static
text node #0: dynamicGenau das braucht Seam am dringendsten und mag es auch am liebsten. Um in React herauszufinden, ob attributes ein static ist, bleibt im Grunde nur rohes Durchprobieren: einmal alle möglichen Eingaben durchlaufen lassen, bleibt attribute unverändert, ist es static, ändert es sich, bekommt es ein slot.
Bei strukturellen Verzweigungen ist es noch schlimmer: Schon die Frage, ob dieses Stück DOM überhaupt da ist, lässt sich nur klären, indem man zweimal rendert und dann diff macht. Und IfBlock von Svelte verwandelt genau diese zweite Sache von beobachtbar in erzeugbar -- das ist der eigentliche Sprung.
Aber so gesagt stimmt das eigentlich nicht ganz, oder es ist zu stark vereinfacht — etwas genauer: ein einzelnes element lässt sich noch weiter aufteilen; und attributes ist nicht zwangsläufig von Anfang an static, das hängt davon ab, wie du es schreibst.
class="card" → attributes: static
class:active={on} → name static, value slot
{...rest} → opaque, escape hatch
text node {user.name} → slotHier ist die erste Zeile im Skeleton zum Beispiel unstrittig, aber erst die zweite Zeile zeigt, wo Svelte wirklich stark ist: class: / style: lassen den Namen attr im AST stehen, nur der Wert wird für sich genommen zu slot, und man muss nicht das ganze element als dynamisch abstempeln. Die dritte Zeile unterscheidet sich nicht grundsätzlich von {...props} in React, Seam behandelt sie einfach als ausdrückliches hatch und muss nicht länger so tun, als würde es sie analysieren; in React muss man das literale className="card" eigentlich auch nicht durchzählen. Durchzählen muss man nur: "ändert sich dieses attr, wenn ein anderer Satz Props kommt". Genau diese letzte Art von Raterei fällt bei Svelte weg – solange der Name im Baum steht, muss man nicht zweimal rendern, um es zu wissen 🫠
Kurz gesagt: React zwingt Seam dazu, vieles wieder als Laufzeitproblem zu behandeln, zum Beispiel ↓
const Wrapper = cond ? A : B;
return Wrapper({
children: foo.map(renderItem)
});Oder auch
return foo && bar
? getLayout()(data)
: something();Natürlich kann der moderne React Compiler vieles davon analysieren, aber ihn umzubauen hieße, diesen Umbau danach dauerhaft pflegen zu müssen, und dieser Aufwand ist schlicht enorm, an Kraft wie an Zeit ... Wenn man stattdessen an Svelte ansetzt, hier habe ich nämlich nicht vor, den Compiler zu forken, legt svelte/compiler den AST theoretisch bereits offen.
Was Seam tun soll, ist: diesen Baum lesen und dann die eigene IR erzeugen; die offizielle DOM- / SSR-Codegenerierung kann man durchaus als Fallback behalten, theoretisch muss nur das IR-Lowering gepflegt werden, nicht das ganze Svelte, also stimmt die Grundrichtung überein und es gibt weniger Konflikte — übereinstimmend ist aber nur, dass die Struktur zur Compile-Zeit sichtbar ist, nicht das Ausführungsmodell; genau deshalb führt das SSR von Svelte / Kit immer noch bei jeder Anfrage die erzeugte render-Funktion aus; in meinem Seam hätte ich dagegen lieber, dass request-time diese Funktion gar nicht ausführt, sondern nur das Protokoll interpretiert (slot + if / each / match)
Bei der Entscheidung für Svelte gab es eigentlich noch eine weitere Überlegung: Unter der Voraussetzung, dass Next.js und React faktisch schon vollständig26 von Vercel kontrolliert und zum Markt-Framework gemacht wurden, müsste meine Architektur, wenn Seam Kompatibilität mit der React-Semantik zusagt, am Ende arbitrary JavaScript → determine tree structure erlauben.
Also müsste die IR von Seam extrem konservativ ausfallen(x). Am Ende liefe es höchstwahrscheinlich darauf hinaus: Was sich statisch analysieren lässt, wird optimiert, was nicht, geht über SSR. Das wäre zwar ebenfalls eine Seite, die CTR und SSR mischt, aber genau an dieser Stelle des „nicht analysierbaren" liegt das Problem, denn in der Welt von React gibt es viel zu viele Stellen, die sich „nicht sicher bestimmen" lassen …
Wenn man den React-Compiler nicht anfasst, bleibt man in derselben Lage wie bei V1: Man behandelt den Compiler als Blackbox und rät blind, was er ausspuckt. Das ist ausgesprochen fragil und unelegant, und Randfälle fliegen einem dabei nur so um die Ohren.
Umkehren
Wenn ich also jetzt sage, dass die Komponentensprache von Seam schlicht Svelte ist, dann kann der Compiler das sogar umgekehrt angehen: zuerst standardmäßig structure known, lokal expression dynamic, an eindeutigen Stellen escape hatch, danach runtime, und erst wenn wirklich nichts mehr geht, als Letztes ein SSR-Fallback.
<script>
let { product } = $props();
</script>
<article class="product">
<h1>{product.name}</h1>
<div class="price">${product.price}</div>
<button>Buy</button>
</article>Seam kann man sich ohne Weiteres ungefähr so vorstellen wie das Folgende
<article class="product">
<h1><!-- slot 0 --></h1>
<div class="price">$<!-- slot 1 --></div>
<button>Buy</button>
</article>Theoretisch gilt dann slot0 = product.name, slot1 = product.price. Genau das ist die Residualisierung, an der ich schon vorher gearbeitet habe: Die Grundidee ist, dass nicht die ganze Seite durch das serverseitige Rendering muss, sondern nur der wirklich dynamische Rest berechnet wird.
Slots und Zweige
Der eigentliche Gewinn beim Wechsel von React zu Svelte liegt aber eine Ebene tiefer; das heißt, auch bei einer Strukturänderung muss man nicht mehr zweimal rendern, um zu raten, denn die Struktur steht schon zur Compile-Zeit fest und man kann direkt die passende IR erzeugen
<article class="product">
<h1>{product.name}</h1>
<div class="price">${product.price}</div>
{#if product.available}
<button>Buy</button>
{:else}
<p>Sold out</p>
{/if}
</article><article class="product">
<h1><!-- slot 0 --></h1>
<div class="price">$<!-- slot 1 --></div>
<!--seam:if:product.available-->
<button>Buy</button>
<!--seam:else-->
<p>Sold out</p>
<!--seam:endif-->
</article>Der Wert ist weiterhin slot, und der Zweig ist der if-Block aus dem alten Protokoll. In der React-Fassung braucht es aber mehrere diff, um an diesen Block heranzukommen; in Svelte dagegen lässt er sich hier direkt aus IfBlock ableiten und erzeugen.
Escape
Das soll natürlich nicht heißen, dass JSX das nicht kann. Nur schreibt es kaum jemand so, dass die Struktur an einer gut sichtbaren Stelle im Verzweigungsbaum stehen bleibt, wenn man es wirklich ausformuliert -- im Grunde landet alles bei so etwas wie hier unten (lol)
const price = formatPrice(product);
const body = product.available
? getAvailableView(product)
: getSoldOutView(product);
return <Layout>{body}</Layout>;Und noch etwas Wichtiges: Selbst wenn du es so schreiben kannst -- solange das Gros von React es nicht so schreibt und die trainierten LLM es nicht so schreiben, wachsen die Migrationskosten und die späteren Entwicklungskosten garantiert exponentiell.
Aber bei Svelte sieht es hier schon wieder anders aus
UI structure ≈ template AST, JavaScript ≈ values + behavior ist hier längst Alltag
Es ist sogar die von Svelte offiziell am stärksten empfohlene Vorgehensweise. Dieser Unterschied ist strukturell dafür, wie Seam die IR herauszieht. Aber bitte versteh das nicht so, als hätte Svelte keine dynamischen Strukturen: Es hat sehr wohl ein entsprechendes Schlupfloch, und zwar als eines seiner zentralen Sprachkonstrukte
{@render children()}
<svelte:component this={layout} />
<svelte:element this={tag}>
{@html html}Wrapper = cond ? A : B verschwindet hier nicht einfach im Nichts, sondern schrumpft von „einem beliebigen JS-Ausdruck" auf genau diese paar Knoten zusammen. Für Seam wird das damit ganz einfach: standardmäßig geht es über AST lowering; was statisch als Loch sichtbar ist, gehört genau dem, dem es dort gehört. Am häufigsten ist {@render} als compose (layout und Card, die children umschließen, sind alle von dieser Art), während <svelte:component> / <svelte:element> / dynamisches {@render} die eindeutigen hatch sind und in runtime oder SSR fallback landen; und für {@html} lässt sich einfach das raw HTML slot weiterverwenden, das ich bereits entworfen habe.
Der eigentliche Vorteil ist also nicht, dass „die Struktur immer bekannt ist", sondern dass „es für die unbekannte Struktur eine Referenzliste gibt" (x
In React ist das Unbekannte das gesamte JavaScript; in Svelte ist das Unbekannte nur die Öffnung, die die oben genannten Escapes lassen, mehr nicht. Setzt man das ein, schrumpft der Umfang dessen, was CTR analysieren muss, ganz sicher deutlich.
CTR
Wer Svelte einsetzen kann, kann deshalb noch lange nicht CTR einsetzen, aber ich habe hier einen Anhaltspunkt ↓
So bleibt die statische Referenz erst compose; hatch läuft weiterhin über den unteren Weg, und daraus ergibt sich, dass die Einheit von CTR der statische Komponentengraph ist und nicht die einzelne Datei. Das meiste ist standardmäßig structure known, ein kleiner Teil ausdrücklich escape hatch, und SSR fallback begegnet einem im Alltag praktisch nie. Alles Übrige, was nicht in der Tabelle steht, wird ausnahmslos als opaque behandelt -- so muss man gar nicht erst raten. CTR ist nur das execution model auf dieser Teilmenge, und selbst wenn ich zu Svelte wechsle, will ich keine Garantie für den gesamten Umfang von Svelte27 geben (x)
CSS
Ein weiterer riesiger Gewinn ist eigentlich CSS: In Svelte lässt es sich so schreiben:
<div class="card">...</div>
<style>
.card { border-radius: 8px; }
</style>Wenn Seam hier eine Komponente kompiliert, verfügt es gleichzeitig bereits über markup dependency + script dependency + style dependency, und der Style ist ein statisches Artefakt. Damit lässt sich äußerst aggressiv ableiten
Man kann sogar noch einen Schritt weiter gehen und den Ablauf so gestalten ↓
Der gesamte oben beschriebene Ablauf kommt vollständig ohne Beteiligung einer JS-Laufzeitumgebung aus.
Kein CSS-in-JS
CSS-in-JS nehme ich ebenfalls nicht an, und das liegt nicht daran, dass React mich zwingen würde, eine Schreibweise wie die folgende zu unterstützen
const Button = styled.button`
color: ${p => p.primary ? "red" : "black"};
`;ODER
<div css={theme => ({
color: theme.colors.primary
})}>Nur lässt sich genau diese Struktur bereits in V1 mit Tailwind umgehen. Der Hauptgrund, CSS-in-JS nicht anzubinden, ist: sobald man es anbindet, wird CSS wieder zum Ergebnis einer JS-Ausführung, und dann müssen Style-Runtime, Registry, SSR-Sammlung, Hashing, Hydration-Konsistenz, Theme-Kontext, Einfügung, Reihenfolge und Deduplizierung allesamt zurückgeholt werden. Ein gewöhnliches Framework kann das schlucken; aber Seam, sie ist eben anders! Genau diese Art von Server- und Runtime-Arbeit will ich beseitigen, und es kann doch wohl nicht nötig sein, aus Höflichkeit gegenüber dem Ökosystem das gerade Vertriebene wieder hereinzubitten (ergibt überhaupt keinen Sinn
Außerdem löste CSS-in-JS zu seiner Zeit genau die Probleme, um die sich heute Tailwind CSS und Motion kümmern. Wenn Tailwind CSS und Motion das inzwischen ausreichend lösen, sogar besser und eleganter, warum dann den Rückwärtsgang einlegen? Wie ich oben schon sagte: „Sobald ein Ökosystem gewachsen ist, tauchen überall Kompromisse28 auf, und nur selten kommt ein Breaking-Refactor, das wirklich erneuert.“ Wenn Seam also noch gar kein Ökosystem hat, warum dann nicht konsequent radikal sein?
Aber ob „radikal“ hier das richtige Wort ist, weiß ich nicht. Dafür spricht: Gegenüber den herkömmlichen Frameworks werfe ich die schwere serverseitige JS-Runtime (Node oder Bun) tatsächlich weg. Dagegen spricht: So etwas wie CSS-in-JS könnte man auch komplett fallen lassen, ohne dass es wirklich schadete – ich sehe darin inzwischen lieber . Die einst bekanntesten CSS-in-JS-Bibliotheken: Stitches wurde im Juni 2023 offiziell als nicht mehr aktiv gepflegt erklärt, styled-components ging im März 2025 in den Ruhezustand über, und bei Emotion sieht es im Grunde genauso aus.
Dann muss ich Sveltes <style> doch einmal loben: CSS → compile-time artifact ist in Svelte im Grunde schon völlig unstrittig (zumindest sehe ich das so, und wenn du anderer Meinung bist, dann hast eben du recht)
Solange es nicht JS → execute → generate CSS → collect → serialize → hydrate ist, wird der Unterschied riesig sein, und noch wichtiger: Vielleicht können die CSS-Abhängigkeiten direkt in die Seam IR wandern 🤔
Zum Beispiel die Kompilierung von Komponenten:
ComponentIR {
skeleton,
dynamic_slots,
css,
client_behavior,
server_dependencies
}Man beachte, dass dies kein zweites, eigenes Rendering-Modell ist: In V1 ergibt skeleton zusammen mit dynamic_slots nach dem Kompilieren genau jenes HTML aus dem alten Protokoll, nur dass für mich nun transparenter ist, wie skeleton plus dynamic_slots zustande kommt.
Nehmen wir an, wir gehen so vor wie unten ↓
<script>
let { name } = $props();
</script>
<div class="user">Hello {name}</div>
<style>
.user { font-weight: 500; }
</style>Dann wird es am Ende zwangsläufig erreicht
ComponentIR
HTML: <div class="user svelte-x">Hello SLOT(0)</div>
CTR: slot(0) = props.name
CSS: .user.svelte-x { ... }
Client: none
SSR: noneBeachte aber: Was du oben siehst, sind nur die Blätter, und dazu ein recht idealer Ausschnitt -- keine Unterkomponenten, keine Strukturverzweigungen, kein Client. Das skeleton auf Seitenebene wird aus dem statischen Komponentengraphen zusammengesetzt; sobald im Teilbaum ein $state oder hatch auftaucht, ist Client / SSR in der Zwischendarstellung eben nicht mehr none.
Aber das kommt dem Idealzustand, den ich für Seam erreichen will, schon sehr nahe: Eine Svelte-Komponente entspricht nicht mehr „einem Renderer, der ausgeführt werden muss", sondern wird zu einer Ressource, die der Svelte-5-Compiler auseinandernehmen kann. Allein das umzusetzen ist für mich bereits eine sehr große konzeptionelle Verbesserung.
Besitzverhältnis
Außerdem hat „kein globaler Zustand“ für Seam eine tiefere Bedeutung als bloß die Developer Experience. Ich bin sicher, in einem Next.js-Projekt oder in irgendeinem React-Full-Stack-Framework ist dir dieser Klumpen hier unten schon einmal begegnet ↓
<ThemeProvider>
<AuthProvider>
<QueryClientProvider>
<I18nProvider>
<RouterProvider>
<App />
</RouterProvider>
</I18nProvider>
</QueryClientProvider>
</AuthProvider>
</ThemeProvider>Natürlich gibt es dafür eine Lösung: Diese "Pyramide" aus verschachtelten Providern lässt sich auch als Compose Providers schreiben. Ungefähr so, wie es unten aussieht.
Konkret wirft man die bisher verschachtelten Provider in ein Array, definiert eine Kompositionsfunktion und schluckt am Ende nur noch diese eine Ebene. Aber für mich ist das eigentlich Selbstbetrug, im Kern wird die Verschachtelung nur "versteckt" und nicht wirklich beseitigt. Aus visueller Verschachtelung wird logische Verschachtelung, in gewissem Sinne also -- reine Selbstberuhigung.
const AppProviders = composeProviders([
ThemeProvider,
AuthProvider,
QueryClientProvider,
I18nProvider,
RouterProvider,
]);
function composeProviders(providers: React.FC<{ children: React.ReactNode }>[]) {
return ({ children }: { children: React.ReactNode }) =>
providers.reduceRight(
(acc, Provider) => <Provider>{acc}</Provider>,
children
);
}
function Root() {
return (
<AppProviders>
<App />
</AppProviders>
);
}Bei React-Projekten endet es wirklich sehr leicht, ja praktisch zu 100 % so, und damit entsteht ein ausgesprochen lästiges Nahtstellen-Problem, nämlich: „Was ist eigentlich die Eingabe einer Komponente?" Oberflächlich könnte man sagen, es sei <ProductCard product={product}/>, als hinge sie nur von product selbst ab? Doch wie sieht es in Wirklichkeit aus
depends on:
product
theme
locale
queryClient
router
auth
featureFlags
...Damit wird der component dependency graph wieder implizit. Für traditional SSR ist das kein großes Problem, im schlimmsten Fall heißt es eben „den ganzen React tree einmal durchlaufen lassen“. Aber worauf Seam am Ende hinauswill, ist die Frage, ob ProductCard eigenständig CTR kann -- und genau da wird es wirklich mühsam. Man könnte sogar sagen, dass man dafür das Verhalten des gesamten React-Compilers sowie die DX und die Gewohnheiten der Nutzer umbauen müsste, und wozu dann das Ganze ...
Wer Next.js benutzt, dem ist die Performance im Grunde schon egal29, und das sage ich mit voller Überzeugung; sie zahlen viel eher für das Drumherum von Vercel, denn wenn du Next.js benutzt und nicht auf Vercel hostest, hast du wirklich nicht den geringsten Vorteil davon 😡
Lokale Ownership
Svelte dagegen macht es sehr leicht, lokale Ownership entstehen zu lassen;
<script>
let { product } = $props();
let quantity = $state(1);
</script>Aus der Perspektive von Svelte und dem Seam Compiler fühlt sich das ausgesprochen angenehm an.
Schließlich ist „Eingabe: product + lokaler Client-State: quantity“die Ownership damit wirklich vollkommen klar
product.name → server CTR
quantity → client
button onclick → client
rest → staticAber das ist nur die Voreinstellung, eine Garantie ist es eigentlich nicht; denn rune module in getContext('theme') und .svelte.ts bleibt eine implizite Eingabe, und QueryClient verschwindet auch nicht einfach von selbst. Ich glaube, Seam wird sich genauso verhalten wie bei der Hatch-Liste weiter oben: Was sichtbar ist, wird in die IR eingetragen (als server-Abhängigkeit oder client-Eigentümerschaft), was sich nicht analysieren lässt, gilt als opaque -- besser jedenfalls, als so zu tun, als gäbe es „in Svelte keine Provider". Aber sicher ist das nicht, vielleicht finde ich bei etwas mehr Recherche doch noch eine Lösung für Provider
Das größte Problem von Seam in der React-Ära war, dass die IR den Weg Component → execute renderer → get output nahm; damit wurde mein sogenanntes seam compiler am Ende in gewissem Sinne zu einem vorgezogenen SSR, oder man kann es auch schlicht bezeichnen als
„universelle SSR-Orchestrierungsschicht“
Gleiches Protokoll
Man kann also ab jetzt sagen: die UI ist Svelte, aber das seam-Protokoll bleibt ein Protokoll, weiterhin vor allem slot und injection, ob JTD wirklich passt, muss ich noch weiter überdenken; von der Ausführungsentscheidung her ist jetzt alles vollständig geschichtet, die Slots sind residual-isiert, Rust / Go / TS server müssen nur blind ausfüllen und den Komponentencode überhaupt nicht verstehen, echtes Zero-JS; gleichzeitig kann man, falls es in der Komponente noch reine Ableitungen gibt, über ein generated JS / QuickJS runtime nachdenken, (Achtung: hier laufen Daten, nicht UI)
Nur was mit hatch markiert ist, wird SSR fallback, und damit muss das Backend, solange kein echtes SSR nötig ist, gar keinen UI-Code ausführen -- also gibt es auch keine Festlegung auf Node ODER Bun. Allerdings (der Go Server soll nicht mehr gepflegt werden, weil meine eigene Kraft dafür nicht reicht. Wenn eine hilfsbereite Person Zeit hat, kann sie ihn aufgreifen und langfristig pflegen -- es liegt nicht daran, dass die Architektur die Backend-Unabhängigkeit nicht mehr trägt)
Anders gesagt: Ich habe nur die UI weggeschnitten, ich nehme jeden an, so eine mühsame und undankbare Sache (?) Der unveränderliche Teil kann weiterhin als „Rendering Protocol“ dienen, das Backend ist nicht auf JS/TS beschränkt.
Runes-Semantik
Ein weiterer sehr wichtiger Punkt ist, dass die Runes von Svelte 5 sich ausgezeichnet mit Seam vertragen, auch wenn Svelte 5 anfängt, ein wenig mehr nach JS auszusehen.
let count = $state(0);
let doubled = $derived(count * 2);Aber dieses JS ist nicht jenes JS30: der Unterschied zu useState / useMemo / useContext / useEffect ist, dass runes weiterhin compiler-recognized semantics sind, sodass Seam wissen kann, dass $state dem client gehört, $derived abgeleitet ist und $effect client runtime ist; das liegt mit const foo = someLibraryHook() nicht in derselben Größenordnung.
Datenherkunft
Ein sehr wichtiger Punkt hier: „erkennen können" heißt noch lange nicht „CTR-fähig". Vor allem sollte man nicht bei jedem $derived automatisch annehmen, es lande in skeleton. Das count * 2 oben ist genau so ein Gegenbeispiel, denn seine Abhängigkeit ist $state, und damit ist es ein clientseitig abgeleitetes Artefakt. Sehen wir uns an, wie ein wirklich CTR-fähiges $derived aussieht:
let { product } = $props();
let price = $derived(formatPrice(product.price));product kommt aus $props, und formatPrice muss außerdem rein und sichtbar sein; an die vorige Tabelle angeschlossen sieht das so aus.
$state / $effect / onclick → client
$derived
deps ⊆ server data, pure, visible → CTR derive
deps include $state → client
impure or opaque callee → QuickJS or SSR fallback
markup → follow the table above; runes are not a free CTR passAufgezeichnet ist es vielleicht leichter zu verstehen(?)
Jedenfalls zeigt sich die Freundlichkeit von Runes darin, dass die Ownership syntaktisch markiert wird, und nicht darin, dass $derived von Haus aus CTR-fähig wäre. Das einzige Kriterium bleibt am Ende doch: Woher kommen die Daten, die der Pfad unten anzeigt, wie rein sind sie, und sind sie sichtbar (lol)
Und daneben kommt dann noch eine Regel <style> → static CSS dependency graph, damit das ganze Weltbild absolut stimmig ist: HTML ist Struktur, CSS ist Stil-Artefakt, JS ist zwingend Berechnung und Verhalten.
Und nicht das, wohin modernes React sehr leicht abgleitet: alles in JS31. Für React selbst mag das durchaus kein Nachteil sein, aber leider gilt: Alles ist JavaScript ist genau die Welt, die für meine aggressive Zerlegung zur Übersetzungszeit am ungünstigsten ist
Was geht in den Versand? 32
Nach all dem Gerede: Was wird am Ende wirklich umgesetzt (haha)? Zuallererst und ohne jeden Zweifel der Wechsel des UI-Stacks auf Svelte, und man darf erwarten, dass danach die Beobachtbarkeit des Compilers deutlich steigt. Der Hauptgrund ist, dass Svelte erzwingt und dazu ermutigt, structure in markup AST zu belassen, statt es in beliebigem JS-Kontrollfluss zu vergraben. Das vergrößert unmittelbar den analysierbaren Spielraum von skeleton / CTR und macht CSS aus runtime concern wieder zu build artifact.
Das heißt, Seam bekommt die Chance auf CSS-Abhängigkeiten pro Komponente, kritisches CSS, Tree Shaking und verzögert geladenes CSS, ohne zusätzlich noch style runtime für server / client pflegen zu müssen.
Und dann wird die Zuständigkeit der Komponenten endlich sauber; weniger Provider / Context / global runtime dependency heißt, dass ich die Chance bekomme, restlos zu beantworten, welcher Wert vom Server kommt, an welcher Stelle eine CTR-Weitergabe möglich ist, wer client boundary braucht (welcher Zustand dem Client gehört) und ob dieses component wirklich hydrate benötigt …
Von diesen dreien entscheiden die ersten beiden unmittelbar darüber, ob CTR nur eine Optimierung bleibt oder wirklich zum wesentlichen Ausführungsmodell33 von SeamJS werden kann. Genau deshalb ist der Verzicht auf React für mich nicht zwangsläufig ein technischer Kompromiss, sondern umgekehrt auch ein struktureller Bruch. Kurz gesagt, ich bleibe bei meinem Satz
Sobald ein Ökosystem erst einmal steht, kommt es bei allem zu Kompromissen,
selten zu einem brechenden Umbau, der wirklich erneuert
Da ich also noch nicht wirklich etabliert bin, habe ich natürlich jede Gelegenheit zu brechenden Änderungen und muss vorerst nicht an Migrationskosten denken; und ich hoffe wirklich, eines Tages nach unzähligen Brüchen den Platz zu finden, der wirklich mir gehört.
Selbst wenn es am Ende nur ich allein benutze, ist es doch mein eigenes „Experiment 🧪"