Laioutr Release News - August 2026
Der August stand im Zeichen der Messbarkeit. Mit dem neuen consent-bewussten Analytics-System bekommt die Plattform ein typisiertes Event-Modell: useAnalytics().track(Token, payload) gibt ein Ereignis ab, dessen Token sein eigenes Payload-Schema mitbringt, und die Auslieferung an jeden einzelnen Empfänger wird erst zum Zeitpunkt der Zustellung gegen die Einwilligung des Besuchers geprüft. Laioutr UI hat die Commerce-Ereignisse einer Storefront direkt darauf verdrahtet — Produktlisten, Produktseiten, Warenkorb und Suche melden ohne jede Konfiguration. Parallel dazu wurde Einwilligung von Cookie-Kategorien auf Verarbeitungszwecke umgestellt, und der ConsentAdapter-Vertrag ist auf vier Mitglieder geschrumpft.
Der zweite Schwerpunkt ist Geschwindigkeit. Orchestr löst die Queries einer Anfrage, die Links einer Query und die Component-Resolver eines Entity-Typs jetzt nebenläufig auf: Der Server-Render einer Storefront-Startseite fiel von rund 724 ms auf rund 322 ms. Dazu kommen um etwa 45 % kürzere Cache-Schlüssel, eine deklarative Cache-Konfiguration, die Orchestr den Schlüssel vollständig selbst bauen lässt, und mehrere Korrekturen an Schlüsseln, die zuvor Markt, Offset oder Sprache nicht enthielten. In Laioutr UI hydrieren Slider und Videos erst in Nähe des Viewports, und Web-Fonts werden nur noch als woff2 ausgeliefert.
Drittens: Beobachtbarkeit. Frontend Core und Orchestr exportieren OpenTelemetry-Traces der serverseitigen Arbeit — Queries, Links und Component-Resolver als verschachtelte Spans, jeder Cache-Zugriff mit einem Verdikt. Und viertens Betrieb: Märkte haben jetzt einen Status (Entwurf oder Aktiv), ein Projekt kann seinen Standardmarkt explizit benennen, und Studio hält den geöffneten Stand — Seite, Auswahl, Markt, Sprache, Vorschau — in der URL.
Frontend Core v0.38.2 – v0.47.2
v0.38.2 wurde am 31. Juli veröffentlicht, aber erst im August dokumentiert und lief deshalb nicht im Juli-Post mit. Sie trägt einen Breaking Change und ist hier aufgeführt.
Highlights
- Consent-bewusstes Analytics-System:
useAnalytics()liefert{ track, register, unregister }.track(Token, payload)gibt ein typisiertes Ereignis ab, dessen Token sein eigenes Payload-Schema trägt;@laioutr-core/core-types/analyticsstellt die Token-Factories und die plattformeigenenweb/*-Ereignisse bereit,@laioutr-core/canonical-types/analyticsdasecommerce/*-Commerce-Vokabular. Empfänger werden mitdefineAnalyticsDestinationregistriert und deklarieren ihren Bedarf alsconsent: { purposes: ['analytics'] }oder überpurposeSetsals ODER-von-UND-Verknüpfung. Die Einwilligung wird pro Empfänger zum Zeitpunkt der Zustellung ausgewertet, nie bei der Abgabe — eine Abgabe fächert sich also genau auf die Ziele auf, die der Besucher erlaubt hat. Ereignisse, die vor der Antwort des Besuchers entstehen, werden gehalten und nach einer Erteilung in ihrer Reihenfolge nachgespielt. Ein Ziel kann alternativonDenieddeklarieren und abgelehnte Ereignisse mit dem Einwilligungszustand entgegennehmen, statt zu verstummen; ein solches Ereignis gilt als zugestellt und wird bei einer späteren Erteilung nicht erneut gespielt, und ein Widerruf lässt dieses Ziel weiterlaufen statt es abzubauen. - Payloads tragen Orchestr-Entitäten direkt:
track(AddToCart, { products: [{ entity: product, quantity: 2 }] })— jede Entität wird zur Abgabezeit auf eine flache Wire-Momentaufnahme projiziert, ausgewählt über ihrenentityType. Trägt eine Entität einen Slug auf ihrerbase-Komponente, bekommt sie zusätzlich eine absoluteurlauf dem Produktions-Host des Markts.@laioutr-core/core-types/orchestrerhält dafürgetEntityComponent(entity, Token)undgetLinkedEntities(entity, LinkToken). Jedes Payload-Schema führt einen optionalencustomFields-Beutel für seiten- oder anbieterspezifische Daten; Schlüssel dort gehören mit einem Namensraum versehen ('acme:productLine'). - Erfassung und Erweiterung: Neu sind die Direktiven
v-track-clickundv-track-impressionsowieuseTrackImpression,useTrackScrollDepthunduseTrackVideoProgress. Ein Umgebungskontext aus Seite, Markt, Sitzung, Einwilligung und Experiment hängt sich an jedes Ereignis;useAnalyticsContexts()ergänzt einen eigenen Provider oder überschreibt einen der plattformeigenen. Drei synchrone Nuxt-Hooks erweitern die Pipeline::emitals Veto,:enrichzur Anreicherung und ein:project-Filter pro Entität, wobeiaugmentProjectioneinen:project-Handler typisiert. - Serverseite und Identität: Der Browser schickt Stapel an
POST /api/frontend/signals; der Pfad ist überanalyticsIngestPathin der öffentlichen Runtime-Config des Moduls überschreibbar, was die Server-Route mitverschiebt. Jedes Ereignis eines Stapels wird für sich beurteilt — ein fehlerhaftes oder zu großes Ereignis kostet nie den Stapel, in dem es mitfuhr.subscribeToAnalyticsregistriert einen Empfänger, der nicht im Browser laufen darf; Handler erhaltensentAtundreceivedAtneben dem Ereignis. Besucher- und Sitzungs-Cookies entstehen ausschließlich unter dem Zweckanalyticsund werden auf die registrierbare Domain des Markts gesetzt, sodass eine Identität einen Sprung zwischen Subdomains übersteht; auf Plattform-Hostnamen bleiben sie host-gebunden. Innerhalb der Studio-Vorschau werden sie cross-site und partitioniert geschrieben. Ein Widerruf löscht beide, und eine späte erneute Erteilung erzeugt frische Tokens, ohne Aktivität rückwirkend zusammenzufügen. - Einwilligung als Verarbeitungszwecke (Breaking): Ein
ConsentAdaptermeldet jetztPartial<ConsentState>übernecessary,functional,analytics,advertisingundpersonalizationund verantwortet die Abbildung aus dem Vokabular seines eigenen Anbieters. Ein Zweck, der in einer Meldung fehlt, gilt als abgelehnt; ein Adapter erteilt einen Zweck, wenn irgendeiner seiner eigenen, der darauf abbildet, erteilt ist. Ein Besucher, der Messung erlaubt und Personalisierung abgelehnt hat, wird Google Consent Mode jetzt als genau das gemeldet — zuvor als Erlaubnis für beide Werbe-Zwecke. - Neuer
ConsentAdapter-Vertrag (Breaking): Ein Adapter besteht aus vier Mitgliedern —name,setup,openConsentUiund optionalhasDecision— und wird mitsetAdapterinstalliert.init,getConsentState,onConsentChange,destroyundisActivefallen zusetup(report)zusammen: das Verdikt des Besuchers sofort und danach bei jeder Änderung melden, optional eine Aufräumfunktion zurückgeben.setupläuft synchron im installierenden Plugin, sodassuseHeadunduseCookieverfügbar sind. Storefront-seitig ersetzt einopenConsentUibeide früheren Overlay-Aufrufe, undadapterNameersetztactiveAdapter. hasDecision()unterscheidet Ablehnung von Schweigen:useConsentStore()legt offen, ob der Besucher den Einwilligungsdialog überhaupt beantwortet hat. Ein Zustand „abgelehnt" ist andernfalls nicht von „nie gefragt" zu unterscheiden — was überall dort zählt, wo dieser Zustand an einen Dritten weitergegeben wird, der seine eigene regionale Vorgabe anwendet. Die Cookiebot- und CCM19-Apps implementieren es und melden eine gespeicherte Ablehnung als Entscheidung statt als Schweigen. Ab 0.42.0 gibthasDecision()boolean | undefinedzurück:undefinedheißt, es existiert überhaupt kein Entscheidungssignal — kein Adapter, oder einer, der es nicht sagen kann.- Jeder Fehlschlag einer Link-Auflösung hat einen Namen:
linkResolver.resolve()gibt weiterhin einen seiteninternen Rückfall zurück statt zu werfen, aber dieser Rückfall hat eine Form —#<code>?<detail>— aus einer geschlossenen Menge, und jeder trägt mit, was den betroffenen Link identifiziert (#unknown-route?pageId=pdp,#missing-required-params?params=brand). Neu istlinkResolver.resolveOrThrow(link, options?)für Aufrufer, die auf den Fehlschlag reagieren können — ein Analytics-Projektor, ein Sitemap-Writer —, das einenLinkResolutionErrormitcodeunddetailswirft. Mit{ withOrigin: true }kommt eine absolute URL statt eines absoluten Pfads zurück; der Origin stammt aus der Domain des Markts, nie auslocation, sodass eine in der Studio-Vorschau oder auf einem Dev-Host aufgelöste Adresse trotzdem die Seite benennt, auf der ein Besucher landen würde. - Märkte im Status Entwurf, plus benannter Standardmarkt: Ein Markt auf
draftliefert weiterhin seinen eigenen Host aus, damit er vor dem Start geprüft werden kann, ist aber aus hreflang-Alternates,og:locale:alternate,x-defaultund Markt-Umschaltern ausgenommen und seine Seiten werden mitnoindex, nofollowausgeliefert.switchMarketUrlgibt für ein Entwurfs-Ziel'#market-not-active'zurück. Der Status wird überRenderMarket.isLinkableundisIndexablegelesen statt über dasstatus-Mitglied.RcProject.defaultMarketIdersetzt die implizite Regel „erster Markt in der Konfiguration" fürx-default, den primären Route-Pfad, den Rückfall bei unbekanntem Host und nuxt-i18nsdefaultLocale— welcher Markt das war, hing zuvor an der Reihenfolge, in der Cockpit sie zurückgab. Bleibt das Feld ungesetzt, gilt das bisherige Verhalten. - OpenTelemetry-Tracing: Eine Storefront, die
OTEL_EXPORTER_OTLP_ENDPOINTzur Build-Zeit setzt, exportiert Traces ihrer serverseitigen Arbeit über die üblicheOTEL_*-Konfiguration; ohne diese Variable wird nichts installiert. Queries, Links und Component-Resolver werden je zu einem Span, verschachtelt unter dem Nitro-Request-Span, und ein Upstream-API-Aufruf während eines Resolvers hängt unter diesem Resolver — ein Trace schreibt Upstream-Zeit also der Arbeit zu, die sie verursacht hat. Attribute sind Zähler und Namen, nach denen ein Backend gruppieren kann: wie viele Entitäten ein Resolver angefragt hat, ein Token-Name, die App, aus der ein Handler kommt. Jeder Cache-Zugriff trägtorchestr.cache.verdict(hit,miss,partial,skip,uncacheable), und ein serverseitig gerenderter Request markiert seinen Span mit Seitentyp, Markt-Slug und Sprache, weil eine Produktseite und eine Listenseite dieselbe Wildcard-Route teilen. - Tracing auf Vercel ist ausdrücklich einzuschalten (Breaking): Eine dort deployte Storefront schaltete es zunächst automatisch ein (0.45.0), was jede Anfrage einen fehlgeschlagenen Span-Export und zwei Fehler-Logzeilen kostete, weil Vercel keinen Collector betreibt, den er erreichen könnte. Ab 0.46.0 ist
LAIOUTR_OTEL_ENABLED=1am Projekt zu setzen, um Tracing zu behalten; ein mitOTEL_EXPORTER_OTLP_ENDPOINTkonfigurierter Build ist nicht betroffen. Ein getracter Request tritt außerdem dem Trace bei, in dem er ankommt, statt einen zweiten, unverbundenen zu beginnen. laioutr://-Ressourcen-Locator:@laioutr-core/core-types/locatorexportiertformatLocatorundparseLocatorsamt der tragenden Typen sowie die KonstanteSTUDIO_CONTAINER_KINDS. Ein Locator benennt einen Namensraum (studioist bislang der einzige), einen Container (pageVariant,section,sectionRef,globalSectionoderblock) über seine ID, einen Pfad aus Objektschlüssel- oder Array-Element-nach-ID-Schritten in dessen Props und optionale Ansichtskoordinaten (locale,market,ref) — etwalaioutr://studio/block/blk_C3/slides[itm_E5]/heading?locale=de. Beide Richtungen beherrschen auch eine relative Form ohne dielaioutr://studio/-Basis.parseLocatorwirft nie, sondern gibt für jede Eingabe{ ok: true, value } | { ok: false, error }zurück.useSectionContext()unduseRenderPageContext()sind auto-importiert: Beide Kontexte wurden zur Laufzeit bereits bereitgestellt, waren aber nur über einen tiefen Importpfad erreichbar. Ein Block erhält nie seine eigene ID, weshalb der Sektions-Kontext für ihn die einzige stabile Listen-Identität ist.- Analytics-Identität serverseitig lesen:
readAnalyticsIdentity(event)gibt die Besucher- und Sitzungs-Tokens zurück, die der Browser auf seinen Ereignissen ohnehin meldet. Eine Server-Route oder ein Connector kann eigene Arbeit damit demselben Besuch zuordnen, ohne eine zweite Identität zu erzeugen. Die Tokens existieren nur unter dem Zweckanalytics— ein leeres Ergebnis heißt also, dass der Besucher ihn nicht erteilt hat. - Entwicklungs-Schalter unter
laioutr.dev: Ein Produktions-Build verwirft das ganze Objekt statt jedes Flag einzeln. Neu istconsentDebug: ein Debug-CMP, das jeden Zweck ohne Nachfrage erteilt. Playgrounds installieren keine App zur Einwilligungsverwaltung, sodass dort nie ein Zweck erteilt wird und die einwilligungsgebundenen Pfade nie laufen. Es meldet eine Entscheidung, die der Besucher nicht getroffen hat, und warnt deshalb bei der Installation.analyticsDebugloggt weiterhin kanonische Ereignisse über das eingebaute Debug-Ziel und greift jetzt nur noch im Dev-Modus. - Sitzungs-Cookies überleben die Studio-Vorschau: Warenkorb- und Kundensitzungs-Cookies werden mit
SameSite=None; Secure; Partitionedausgestellt, wenn die Anfrage aus dem Vorschau-Frame kommt, sodass ein im Editor aufgebauter Warenkorb Reloads übersteht. Die Vorschau bekommt ihre eigene Cookie-Partition und bleibt damit von der echten Sitzung im selben Shop im selben Browser getrennt. App-Autoren erhalten die beiden neuen Server-Auto-ImportssetManagedCookieunddeleteManagedCookie, die diese Richtlinie anwenden — Löschungen müssen zwingend überdeleteManagedCookielaufen, weil ein Delete ohnePartitionedden falschen Cookie-Jar adressiert und den Cookie stillschweigend stehen lässt.Securewird jetzt aus dem Request-Origin abgeleitet statt pro Connector gesetzt. menuTreeAtDepth(items, startLevel): Auto-importiert nebenbuildMenuTree, baut den Menübaum und steigt dannstartLevelEbenen ab, sodass die Knoten dieser Tiefe zur obersten Ebene werden — gedacht zum Überspringen eines synthetischen Upstream-Wurzelknotens (etwa einer Magento-Kategorie „Root"), damit die erste geschäftlich relevante Ebene oben rendert.0(die Vorgabe) lässt den Baum wie geholt.- Eine Seite mit einer einzigen Variante holt in einem Request: Seiten-Queries und Varianten-Queries laufen dort jetzt in einer Anfrage statt in zwei seriellen Runden, sodass die Seite früher rendert. Seiten mit mehreren Varianten bleiben unverändert — ein
frontend-core:page-renderer:select-page-variant-Handler liest weiterhin die aufgelösten Seiten-Queries, bevor er wählt.
Fehlerbehebungen & Verbesserungen
- Behoben: Der Reflect-Endpunkt lieferte den Sektions- und Block-Katalog eines vorherigen Deployments aus. Seine gecachte Reflexion wird jetzt nach Build-ID geschlüsselt, sodass ein Redeploy ein Cache-Miss ist statt zu erben, was der letzte Build hinterlassen hat, und zwei Frontends, die ein Redis teilen, überschreiben ihre Einträge nicht mehr gegenseitig. Einträge verfallen nach 12 Stunden.
- Behoben: Die App-Konfiguration aus
laioutrrc.jsonwurde in jedes App-Modul doppelt gemischt. Sie wurde sowohlnuxt.options[<appName>]zugewiesen als auch aninstallModuleübergeben, sodass Nuxt sie mit sich selbst mischte und jede Array-Option verkettete — eine vierstellige Shopify-sortings-Liste kam als achtstellige an und ließ den Build an einem Schlüsselduplikat scheitern. - Behoben:
linkResolver.resolve()verwarf diequeryeines Links. Weil jede Laioutr-SeitenroutelocalizedPathsträgt, liefen interne Links immer über den Zweig für lokalisierte Pfade — derqueryignorierte und vor dem vue-router-Rückfall zurückkehrte, der sie angewandt hätte. Eine Header-Suche landete auf/searchohne?q=.url- undanchor-Links tragen ihrequerynun ebenfalls; sie wird in das bestehendehrefgemischt (diequerydes Links gewinnt bei einer Schlüsselkollision) und landet immer vor dem Fragment. - Behoben: Der Render-Cache des Seiten-Endpunkts wuchs unbegrenzt und konnte den Speicher einer Storefront erschöpfen. Der Endpunkt merkte sich jede gerenderte Seite je Seite, Markt und Sprache in einer Map, aus der nie verdrängt wurde — und Modul-Scope lebt auf einem serverlosen Host so lange wie die Instanz. Die Map ist jetzt begrenzt.
- Behoben: Der Consent-Store ist auf die Nuxt-App statt auf das Modul begrenzt, sodass auf dem Server jede Anfrage ihren eigenen bekommt. Zuvor war er modulglobal — auf einem Server also prozessglobal: Jeder gleichzeitige Render teilte einen Store, der vom ersten Request installierte CMP-Adapter bediente mit der damals erfassten Cookie-Ref alle späteren, und jeder weitere Request hängte ihm einen zusätzlichen Consent-Listener an.
- Behoben: Ein Markt- oder Sprachwechsel im Studio lädt den vorgeschauten Inhalt neu. Die Vorschau renderte weiterhin den Markt, mit dem sie geöffnet wurde — ein Wechsel von Deutsch auf Niederländisch ließ die vorigen Menüs, Produktdaten und Preise stehen, sodass die Vorschau den Inhalt eines Markts unter den Einstellungen eines anderen zeigte. Orchestr-Ergebnisse sind auf Markt und Sprache begrenzt, der Client cachte sie aber unter einem Schlüssel, der keines von beidem trug. Die gecachten Ergebnisse werden bei einer Änderung der Auswahl jetzt verworfen. Das Storefront-Rendering ist nicht betroffen.
- Behoben: Die kanonischen Seitentypen und die Analytics-Projektoren registrieren sich im Browser. Das Plugin, das sie zog, nutzte einen dynamischen Import mit
@vite-ignoreund einem Template-Literal-Specifier, sodass Vite den nackten Specifier unangetastet ließ und der Browser ihn nicht auflösen konnte. Der Fehlschlag lief in ein leerescatch, es registrierte sich also nichts, und jede Entität in einem Analytics-Payload fiel auf ihre ID und ihre Adresse zurück. Seitentypen waren nicht betroffen, weil sie auf dem Server auflösen. Das Modul prüft jetzt zur Build-Zeit, ob das Paket installiert ist, und emittiert dann ein Plugin, das es statisch importiert; das Paket bleibt optional. - Behoben: Eine Seite führt keine Query mehr aus, die nur noch ein verwaistes Feld bindet. Lässt eine Sektion oder ein Block ein Feld aus ihrem Schema fallen, bleibt der gespeicherte Wert in der Projektkonfiguration — und dieser Wert hielt die Query am Leben, sodass jeder Render Daten holte, die kein Component liest. Ein Header, der auf diesem Weg noch eine Warenkorb-Bindung hielt, kostete einen Upstream-Request auf jeder Seite der Storefront. Eine Query überlebt jetzt so lange, wie irgendein lebendes Feld oder ein SEO-Platzhalter sie referenziert; eine Sektion oder ein Block, den keine Registry kennt, verliert seine Queries ebenfalls. Jeder Wegfall warnt einmal und benennt Component, Feld und Query-Token.
- Behoben: Die Studio-Vorschau scheiterte, wenn das Handshake-Secret nicht in der URL stand. Sie autorisiert jetzt über die Embed-Markierung, die der Server nach der Prüfung dieses Secrets ausstellt, sodass ein Reload oder eine clientseitige Navigation innerhalb des Frames weiter funktioniert und eine außerhalb von Studio geöffnete Storefront normal rendert statt
No secret providedzu werfen. Die Markierung bleibt auf ein Projekt begrenzt. Der Projekt-Secret erreicht jetzt kein Client-JavaScript mehr und steht damit nicht länger im Hydration-Payload einer eingebetteten Seite. - Behoben: Die Analytics-Identitäts-Cookies überleben auf der geteilten Domain einer Hosting-Plattform. Eine von einem Host wie
example.vercel.appoderexample.pages.devausgelieferte Storefront begrenztelaioutr_vidundlaioutr_sidauf das Plattform-Suffix selbst, was ein Browser als ungültige Cookie-Domain ablehnt — auf diesen Hosts existierte also keines der beiden Cookies und kein Besucher- oder Sitzungs-Token erreichte ein Ereignis. Die Cookie-Domain wird jetzt gegen den privaten Abschnitt der Public Suffix List aufgelöst, dieselbe Sicht, die ein Browser anwendet. - Behoben: Die konfigurierte Sortierung einer Query wird angewandt. Der auf der Query gespeicherte Wert fiel beim Bauen des Requests weg, sodass eine in Studio gesetzte Sortierung — oder eine als
defaultSortingaus einemqueryTemplateProviderzurückgegebene — den Query-Handler nie erreichte. Sie wird jetzt alssortder Query mitgeschickt; eins-URL-Parameter hat weiterhin Vorrang, die konfigurierte Sortierung setzt also die Vorgabe und keine Festlegung. Links sind nicht betroffen. - Behoben: Der
referrereines Seiten-Ereignisses ist bei jedem Seitenaufruf eine absolute URL, nicht mehr nur beim ersten. Innerhalb einer SPA-Sitzung meldete er die vorige Route als nackten Pfad, was ein Ziel, das Referrer klassifiziert, als fehlerhaft liest statt als Besuch von derselben Seite. - Behoben: Eine Prop-Änderung im Studio aktualisiert die Vorschau wieder. Jede Änderung renderte die Seitenvariante neu, die
PageRendererbeim Öffnen der Vorschau erfasst hatte, sodass sich ohne Reload nichts auf der Seite bewegte. - Ein Request-Span übernimmt einen eingehenden Trace-Kontext nur, wenn nichts weiter oben schon einen geöffnet hat. Wo eine HTTP-Instrumentierung bereits einen Server-Span begonnen hat — ein Node-Deployment, das an einen OTLP-Endpunkt liefert —, bleibt dieser Span der Elternknoten, statt den Request an den entfernten Aufrufer umzuhängen.
- Das Start-Banner erscheint nicht mehr während
nuxi prepareundnuxi typecheck, sondern nur noch, wenn die App tatsächlich bootet.
Hinweise für Entwickler (Breaking Changes)
@nuxtjs/robotswird nicht mehr installiert (0.38.2):robots.txt, derX-Robots-Tag-Header und der Route-Rule-Wertrobotskommen jetzt aus der App@laioutr/app-essentials-seo— diese installieren, um sie zu behalten, und über deren App-Config statt übernuxt.options.robotskonfigurieren. Ein Frontend mit weder dieser App noch einem eigenen@nuxtjs/robots-Install liefert keine/robots.txtaus (ein 404 sagt Crawlern, dass sie alles crawlen dürfen — genau das, was der bisherige Vorgabeinhalt aussagte), und jederrobots-Schlüssel in dernuxt.configoder in einer Route-Rule ist stillschweigend wirkungslos.robots-Meta-Tags auf Seitenebene sind nicht betroffen; sie kommen aus den SEO-Einstellungen der Seitenvariante.- Die spezialisierten Tracking-Composables sind entfernt (0.42.0): Statt
const { trackAddToCart } = useProductInteraction(); trackAddToCart(payload)jetztconst { track } = useAnalytics(); track(AddToCart, payload). ConsentAdapter-Vertrag (0.42.0): Die CMP-Apps zusammen mit@laioutr-core/frontend-coreaktualisieren — ein gegen den alten Vertrag geschriebener Adapter installiert nicht mehr.registerAdapterplusactivateAdapterwerden ein synchronessetAdapter, das das Handle zum Entfernen des Adapters zurückgibt;deactivateAdapterentfällt. Wirftsetup, warnt der Store und verwirft den Adapter. Storefront-seitig:consentStore.openConsentUi()undconsentStore.openConsentUi('preferences')ersetzenshowConsentOverlay()undrenewConsent().hasDecision()gibtboolean | undefinedzurück — Code, der ein falsy-Ergebnis als Ablehnung gelesen hat, muss die beiden Fälle unterscheiden.- Einwilligung als Zwecke statt Cookie-Kategorien (0.42.0):
ConsentManagementStateundhasCategoryConsentsind entfallen. Aus{ necessary, functional, statistics, marketing, unclassified }wird{ necessary, functional, analytics, advertising, personalization }. fillParamsgibtundefinedzurück, wenn ein Pflicht-Parameter keinen Wert hat, statt ihn mit einer Leere zu füllen (0.42.0). Die Leere fiel in den benachbarten Trenner zusammen — aus/:brand/p/:slugwurde/p/shoe, eine Adresse, die auflösbar aussah und es nicht war. Aufrufer müssen jetzt aufundefinedprüfen. Endliche Mengen-Vorgaben (/:page(a|b)) und optionale Parameter (:lang?,:rest*) sind nicht betroffen;missingRequiredParams(path, params)benennt die fehlenden. Nachgelagert: Ein hreflang- oder Canonical-Link, dessen Parameter nicht gefüllt werden können, wird weggelassen statt abgeschnitten emittiert, und ein Sprachumschalter bietet die Startseite der Ziel-Domain statt eines defekten Pfads.RenderI18nConfig.marketsenthält nur noch verlinkbare Märkte (0.41.0). Die vollständige Liste inklusive Entwürfe liegt inRenderI18nConfig.allMarkets. Code, der einen Markt-Umschalter rendert, braucht keine Änderung und beachtet den Status automatisch; Code, der jeden konfigurierten Markt braucht (Routing, Host-Auflösung, Vorschau), muss aufallMarketswechseln.- Lokale Dev-Hostnamen (0.41.0): Für Projekte auf
*.app.laioutr.techfällt dieses Suffix weg, statt in das Label gefaltet zu werden. Ein Markt aufexample-shop.app.laioutr.techist jetzt unterexample-shop.local.laioutr.techerreichbar statt unterexample-shop-app-laioutr-tech.local.laioutr.tech. Derselbe Host treibt Marktauflösung und Dev-Cookie-Domain, alle drei bleiben also im Gleichschritt — Lesezeichen undallowedHosts-Einträge, die die alte Form nannten, aktualisieren. - Ein nicht beanspruchter Pfad unter
/apioder/.well-knownantwortet mit einer kleinen HTML-404 (0.47.0) statt mit der vollständigen 404-Seite des Projekts samt Sektionen und Queries. Die Antwort trägtCache-Control: public, s-maxage=3600, sodass ein CDN eine Wiederholungsanfrage ohne Funktionsaufruf beantwortet. Eine registrierte Server-Route und eine Datei inpublic/gewinnen weiterhin, brauchen also keine Änderung. Ein/.well-known/…-Pfad, der von einer Nuxt-Page ausgeliefert wird, gewinnt nicht: nachpublic/oder in eine Server-Route verschieben. - Tracing auf Vercel ist opt-in (0.46.0):
LAIOUTR_OTEL_ENABLED=1am Vercel-Projekt setzen, um es zu behalten.
Orchestr v0.38.2 – v0.45.0
Orchestr und Frontend Core werden im Gleichschritt veröffentlicht; diese Sektion führt nur, was ausschließlich im Orchestr-Changelog steht. Wie bei Frontend Core wurde v0.38.2 am 31. Juli veröffentlicht und erst im August dokumentiert.
Highlights
- Nebenläufige Auflösung: Die Queries einer Anfrage, die Links einer Query und die Component-Resolver eines Entity-Typs lösen jetzt nebenläufig auf statt hintereinander. Eine Seite wartet auf ihren längsten Strang statt auf die Summe ihrer Arbeit. Der Server-Render einer Storefront-Startseite fiel von rund 724 ms auf rund 322 ms; die sechs Component-Resolver einer kalten Produktseite waren nach 378 ms fertig — hintereinander ausgeführt summieren sie sich auf 937 ms. Auf oberster Ebene laufen bis zu sechs Queries gleichzeitig. Chunks verschachteln sich dadurch: Ein Client, der sie nach
patheinordnet und Entity-Chunks wie ohnehin nötig nach ID mischt, ist nicht betroffen; einer, der sich darauf verlässt, dass Chunks nach Query gruppiert und in Request-Reihenfolge ankommen, ist es. - Ein fehlschlagender Link verwirft seine Query nicht mehr: Ein werfender Link-Handler ließ die ganze Query in einen Fehler-Chunk kollabieren und nahm dabei das Query-Ergebnis und jeden Entity-Chunk mit, den seine Geschwister-Links schon gestreamt hatten. Er meldet den Fehler jetzt an seinem eigenen Pfad,
[queryId, linkToken], und der Rest der Query läuft durch. Scheitern mehrere Teile einer Query, meldet jeder seinen eigenen Fehler statt nur der erste. - Deklarativer Cache: Ein Query- oder Link-Handler schaltet seinen Cache mit einer TTL und einer Strategie ein, und mit nichts weiter —
cache: { ttl: '1 day', strategy: 'ttl' }. Orchestr baut den ganzen Schlüssel: Token, Umgebung, angefragter Ausschnitt, Sortierung, Filter und Token-Eingabe. Gecacht wird standardmäßig der erste, ungefilterte Ausschnitt einer Liste; das Ende einer Liste ist kalt und eine Filterkombination unbegrenzt, beides wird deshalb aktiv hinzugenommen (pages: 'all',filters: ['filter.v.availability']) statt abgewählt.shouldBypassCacheverweigert einen Request, den die deklarativen Optionen zulassen würden; es läuft danach und kann sie nur einengen. validateentscheidet aus dem Ergebnis: Eine Query- oder Link-Cache-Konfiguration nimmtvalidatean, das mit dem Handler-Ergebnis aufgerufen wird, bevor es geschrieben wird. Gibt esfalsezurück, wird das Ergebnis ausgeliefert, aber nicht gespeichert.buildCacheKeyentscheidet Cachebarkeit bereits aus dem Request, bevor der Handler läuft; das hier entscheidet sie aus dem Ausgang — ein degradierter Rückfall, der nicht für den Rest seiner TTL ausgeliefert werden darf, oder ein Ergebnis, dessen Erzeugung nichts kostete. Es steuert nur den Schreibvorgang: Ein bereits im Cache liegender Eintrag liefert weiter aus, bis seine TTL abläuft.cacheKeysfür handgebaute Schlüssel:useUserlandCachegibt einen nackten Storage ohne jedes Prefixing zurück. Ein dort gebauter Schlüssel trägt die Umgebung selbst mit, oder er liefert die Daten einer Storefront an eine andere, und er maskiert eine ID mit/oder:, oder unstorage schreibt sie in einen anderen Schlüssel um.cacheKeys.forClientEnv(clientEnv)undcacheKeys.escape(id)schließen das.cacheKeys.forEntityIdsmacht aus einer Menge von Entity-IDs ein kurzes Segment fester Länge, sodass ein handgebauter Schlüssel nicht mit der Seitengröße wächst — ein Handler, der IDs stattdessen verkettete, produzierte für 48 Produkte über zweitausend Zeichen, jenseits der 255 Bytes, die ein Dateisystem für ein Pfadsegment erlaubt, und hinein in das Request-Größenlimit eines gehosteten Redis. Query- und Link-Handler brauchen das nicht: Orchestr schlüsselt die Quell-IDs eines Links selbst.- Cache-Schlüssel sind rund 45 % kürzer: Gemessen an zwei Live-Storefronts fiel ein Component-Schlüssel auf einer von 144 auf 84 Bytes und auf der anderen von 121 auf 66, ein Produkt-Varianten-Link-Schlüssel von 136 auf 82, ein Page-Index-Schlüssel um 17 %. Der Schlüssel machte ein Drittel der Speicherkosten eines Component-Eintrags aus, und jeder Schlüssel wird bei jedem gebündelten Lesen erneut geschickt — die Ersparnis landet also auf der gespeicherten Größe und auf dem Request-Payload gleichermaßen. Kürzer wurden drei Dinge: der Namensraum, die Maskierung (zwei Bytes für die vier Zeichen, die unstorage umschreiben würde, statt drei Bytes für jedes Zeichen, das
encodeURIComponenterkennt — eine Shopify-GID zahlte 15 Bytes für 5 Zeichen) und das Umgebungssegment, in dem Sprache, Währung, Markt und Vorschaustufe zu einem 8-Zeichen-Digest werden. Entity-Typ, ID, Component und Seitentyp bleiben lesbar. - Cache-Report auf Anfrage-Ebene: Eine Execution Summary trägt neben ihren Query-Zusammenfassungen einen Cache-Report für die Anfrage. Er beantwortet, was die Query-Zusammenfassungen nicht konnten — ob ein Component-Lesen als ein gebündelter Aufruf oder als viele einzelne ankommt, wie die Trefferquote eines bestimmten Components aussieht, und ob ein Hintergrund-Schreibvorgang verloren ging, als das Isolate einfror. Die Zähler werden nur für eine Anfrage erhoben, die
options.dev.enableSummarygesetzt hat; jede andere zahlt nichts, die Payload-Messung eingeschlossen. - Passthrough ist auf den schreibenden Handler begrenzt: Jeder Handler einer Query teilte zuvor einen
passthrough-Speicher. Ein Handler liest jetzt jedes Token, das seine Aufrufer gesetzt haben, und schreibt dorthin, wo nur die Handler unter ihm lesen können. Ein Link-Handler kann seinen eigenen Component-Resolvern also Daten übergeben, ohne dass sie den Rest der Query erreichen. Im Gegenzug sehen zwei Links, die dasselbe Token setzen, den Wert des jeweils anderen nicht mehr.
Fehlerbehebungen & Verbesserungen
- Behoben: Ein
TypeError: Cannot read properties of undefined (reading 'length')ließ jede Query scheitern, deren Link-Handler mitentity,entitiesoder einer einzelnentargetIdantwortet. Die in 0.44.0 ergänzte Telemetrie zählte die Ziele eines Links übertargetIdsauf der Rohantwort des Handlers, was nur dietargetIds-Form trägt. Die scheiternde Query gab keine Daten zurück und meldete einen Fehler-Chunk, sodass eine darauf gebaute Seite ohne ihre Produkte renderte. - Behoben: Query-, Link- und Entity-Component-Caches schlüsseln jetzt auf den Markt, Query- und Link-Caches zusätzlich auf das Paginierungs-Limit. Zwei Märkte, die Sprache und Währung teilen, lesen sich ihre gecachten Ergebnisse nicht mehr gegenseitig, und eine Anfrage nach 24 Elementen bekommt nicht mehr den Ausschnitt, den eine andere Seitengröße gecacht hat.
- Behoben: Der angefragte Offset fehlte in jedem Query- und Link-Schlüssel, sodass
?offset=5&limit=24dasoffset=0-Ergebnis ausgeliefert bekam. Und dasgetKeySuffixeines Component-Resolvers ersetzte den Umgebungs-Digest statt ihn zu erweitern, was zwei Märkte auf einem Eintrag kollidieren lassen konnte. - Behoben: Ein gestreamtes Query-Ergebnis wird gerendert, sobald es sich gesetzt hat, statt einmal pro Antwort-Chunk. Auf einer serverseitig gerenderten Seite renderte die erste clientseitige Navigation, die eine Query ausführte, bei jedem Chunk neu — einschließlich des Fensters, in dem die Entity-IDs eines Links angekommen sind, die Entitäten selbst aber noch keine Components melden. Sektionen, die diese Components lesen, renderten gegen diesen halb geladenen Zustand und warfen, was sich als sporadisch las, weil ein Retry meist nach dem Ende der Antwort landete.
- Behoben: Ein Projekt, das seinen Cache auf ein echtes Backend richtet, behält es in der Entwicklung. Orchestr mountete bei jedem Dev-Boot ein In-Memory-LRU über seinen eigenen Cache-Namensraum, und dieser Mount ist spezifischer als der
cache-Mount, den ein Projekt konfiguriert — er gewann also. Dev lief damit nie den Pfad, den Produktion läuft: keine Round-Trips, keine Serialisierungsgrenzen, und ein konfiguriertes Redis, das nichts empfing. Das LRU mountet weiterhin, wenn kein Backend konfiguriert ist. - Behoben: Ein
pageIndex.locate-Lookup liefert die Seiten-Metadaten einer Sprache nicht mehr an eine andere. Das gecachte Ergebnis trägt in der Sprache aufgelöstemeta, der Eintrag war aber allein auf den Markt geschlüsselt. Zwei Sprachen eines Markts kollidieren, sobald sie einen Route-Parameter teilen — was auf jedes Produkt zutrifft, dessen Slug eine SKU, ein Markenname oder eine unübersetzte Modellnummer ist. Der Schlüssel deckt jetzt die Sprache mit ab, und Vorschau- und veröffentlichte Ergebnisse teilen ebenfalls keinen Eintrag mehr. - Behoben: Cache-Schreibvorgänge überleben auf Vercel. Orchestr schreibt nach dem Antworten über
event.waitUntilin den Cache, und auf Vercels Node-Runtime hielt nichts die Funktion am Leben, um sie zu beenden — Nitros Vercel-Preset setzt dasevent.context.waitUntilnie, nach dem Nitro selbst sucht, sodass der Schreibvorgang als freischwebendes Promise mit der Invocation starb. Gegen eine deployte Sonde auf einer kalten Instanz gemessen ging der verzögerte Schreibvorgang in vier von vier Versuchen verloren, der unmittelbare in zwei davon. Ein Nitro-Plugin liefert diesen Hook jetzt aus Vercels eigenem Request-Kontext, es braucht keine zusätzliche Abhängigkeit, tut abseits von Vercel nichts und tritt hinter jede Plattform zurück, die selbst einen bereitstellt. - Behoben: Devtools-Traces und Execution Summaries beschreiben, was tatsächlich passiert ist. Eine abgeschlossene Query meldete
unknownfür ihre ID und ihr Token, die fehlenden Entity-IDs eines Component-Resolvers fielen weg, und nebenläufig gestartete Spans erschienen als Kette — unterPromise.alllaufende Initwares wurden ineinander verschachtelt dargestellt statt nebeneinander. - Behoben (0.38.2):
listPagesFrommeldet seinenendCursoran jeder Abbruchposition, nicht nur an dertake-Grenze. Ein Konsument, der früh aufhörte — etwa an einem Wall-Clock-Budget —, lasendCursoralsundefinedund konnte das nicht von einer erschöpften Aufzählung unterscheiden, sodass ein Teil-Durchlauf als vollständig verbucht wurde. Zwei Felder machen den Ausgang eines Durchgangs eindeutig:exhaustedist das Abbruchsignal einer Akkumulationsschleife,progressedmeldet, ob der Durchgang dauerhaft vorangekommen ist. Eine Schleife muss sowohl bei!progressedals auch beiexhaustedstoppen, sonst wiederholt sich ein Durchgang, der nichts nimmt, endlos.
Hinweise für Entwickler (Breaking Changes)
- Die Storage-Namensräume sind umgezogen: Ein Projekt, das einen Treiber unter
cachemountet, deckt beide ab und braucht keine Änderung. Ein Projekt, das unter dem vollen Pfad mountet, schon — aus'cache:orchestr:internal'wird'cache:orch:i'. Ein auf einem alten Pfad belassener Treiber empfängt nichts, und der Cache fällt auf zurück, wascachebedient — im schlechtesten Fall ein Speichertreiber pro Isolate, der keinen Fehler meldet und zwischen Anfragen nichts hält. buildCacheKeyist jetzt optional und liefert ein abschließendes Segment statt des ganzen Schlüssels. Ein Handler, der es behält, funktioniert weiter, und einnull-Rückgabewert verweigert den Cache nach wie vor.- Jeder Cache-Schlüssel ändert seine Form, in 0.43.1 wie in 0.45.0. Bestehende Einträge werden nie wieder gelesen und verfallen unter ihrer eigenen TTL — nach dem Deployment ist mit einem einmaligen Cold-Cache-Fenster zu rechnen; von Hand geleert werden muss nichts.
ExecutionSummaryist jetzt eine Union: vor dem Lesen der Query-Felder auftypeeinengen (if (summary.type === 'query') summary.linkSummaries;).- Chunk-Reihenfolge: Chunks verschiedener Queries verschachteln sich jetzt. Ein Client, der Chunks nach
pathliest, ist nicht betroffen; einer, der auf Gruppierung nach Query in Request-Reihenfolge baut, ist es. - Geteiltes
passthroughentfällt: Ein Handler, der das Token eines Geschwister-Links gelesen hat, bekommt jetzt seinen eigenen Vorgabewert.
Laioutr UI v2.8.6 – v2.22.0
Highlights
- Commerce-Ereignisse ohne Konfiguration: Produktlisten, Produktseiten, Warenkorb und Suche melden jetzt Commerce-Ereignisse. Die Instrumentierung ist immer an und hat keine Studio-Oberfläche. Produkt-Slider und das Produkt-Grid melden
view_item_list, sobald sie auf dem Bildschirm sind — nicht bloß, wenn sie gerendert wurden. Ein Tile meldetselect_itembei einer Navigation undadd_to_cart, nachdem die Mutation aufgelöst ist. Eine Produktseite meldetview_itembeim Laden und erneut bei jedem Variantenwechsel. Der Warenkorb meldetview_cartbeim Öffnen,begin_checkoutan der Handlungsaufforderung und für jede Mengenänderung eine Entnahme oder eine Hinzunahme, bemessen an dem, was sich bewegt hat, statt an dem, was übrig bleibt. Der Header meldet eine abgeschicktesearch, eine Suchergebnisseiteview_search_results. Rohe Entitäten gehen intrack(), und die Projektor-Registry macht daraus die Wire-Formen — keine Aufrufstelle baut ein Payload von Hand. MediaFeed: Ein Vollbild-Feed für kurzformatige Medien im TikTok/Reels-Muster — vertikales Scroll-Snapping, immer nur ein spielendes Element, eine Aktionsleiste, ein Stummschalter und ein „Mehr lesen"-Sheet. Nur das sichtbare Element ist interaktiv, die anderen sindinert, sodass Tabben nie ein Steuerelement außerhalb des Bildschirms erreicht, und jedes Element trägtdata-state="active" | "inactive". Rendern lässt sich der Feed aus Props (items+v-model:open) oder von irgendwo auf der Seite überuseMediaFeedStore()steuern. Weil Browser Autoplay nur stumm erlauben, startet der Feed still und die obere Leiste trägt einen Weg zurück zum Ton. MiturlParamwird das sichtbare Element mit der URL synchronisiert (?reel=<id>), sodass es sich teilen lässt und der Zurück-Button den Feed schließt statt die Seite zu verlassen — der Parameter trägt die Element-ID, nicht deren Position, ein Link übersteht also eine Umsortierung. Die zusammengesetzten Teile sind für eigene Kompositionen exportiert.- Neu im UI Kit an der Seite von
MediaFeed:useOverlayHistorysynchronisiert den Offen-Zustand und das aktuelle Element eines beliebigen Overlays mit einem Query-Parameter — Öffnen legt einen History-Eintrag an, Elementwechsel ersetzen ihn, und Zurück schließt das Overlay statt wegzunavigieren.AnimatedIconspiegelt einen booleschen Aktivzustand, mit einer Überblendung beim Übergang und einer nach oben wegfliegenden Burst-Animation, in Vielfachen der Icon-Größe bemessen und über--animated-icon-active-coloreinfärbbar.AnimatedButtonistButtonmit einem dauerhaften Pulsieren, das bei Hover, unterprefers-reduced-motionund überpausedanhält.$count(1234)formatiert Zahlen kompakt für die aktuelle Sprache und beachtet eigenenumberFormats; die Vorgabe-Abkürzung ist die von CLDR und unterscheidet sich je Sprache — Englisch kürzt ab tausend (1.2K), Deutsch erst ab einer Million.Badgebekommt eineglass-black-Variante für Badges über beliebigen Medien, undMediasowieMediaVideobekommenv-model:currentTimeals Geschwister vonv-model:paused: Zuweisen springt, und das Element meldet seinen eigenen Fortschritt zurück. - Vier neue Locale-Bundles: Französisch, Italienisch, Spanisch und Polnisch — Storefronts in diesen Sprachen rendern ihre UI-Chrome nicht länger auf Englisch.
ui-kitlieferte nurde,enundnl, und jede andere Sprache fiel aufendurch: 257 Meldungen über 33 Komponenten, darunter das Suchfeld, der Warenkorb, „Add to cart", „Sold Out", die Filter-Schublade und die Header-Beschriftungen für Konto, Wunschliste und Warenkorb. Der Seiteninhalt war übersetzt, die Chrome darum herum nicht. Die neuen Bundles decken alle 257 Schlüssel ab, Schlüssel für Schlüssel gegenen.tsverifiziert und mit jedem{placeholder}-Token erhalten. Zwei bestehende Lücken sind gleich mitgeschlossen:defehltensliderNavigation.play/.pause,nlder ganzemediaFeed-Block. Für bestehende Projekte ist das keine Verhaltensänderung —de,enundnllösen genau wie vorher auf. Zuvor kam bereits (2.11.0) das niederländische Bundle für das UI Kit hinzu. - Das Produkt-Tile pinnt eine Variante nur dann in seinen Link, wenn der Connector sie autorisiert hat: Ein Tile in einer Liste, einem Grid oder einem Slider erzeugte für jedes Produkt
/<slug>?variant=<variant-id>. Der Wert kam ausdefaultVariant, den die meisten Connectoren aus dem Lagerbestand ableiten — dasselbe Produkt änderte also seine URL, sobald eine seiner Größen ausverkauft war. Eine abgeleitete Vorgabe bleibt jetzt aus dem Link, und jede Listen- und Slider-URL dieser Produkte ändert sich. Eine autorisierte Vorgabe ist die Wahl eines Händlers und wandert nicht mit dem Bestand, der Link trägt sie also weiter. Ein ausdrückliches?variant=in einem geteilten Link, einer Anzeigen-Landing-URL oder einem Wunschlisten-Eintrag bleibt unangetastet. Passend dazu öffnet eine ohne?variant=aufgerufene Produktseite jetzt die Vorgabe-Variante des Produkts statt dessen erster, und ein Farb-Swatch wird nur für eine autorisierte Vorgabe-Variante als ausgewählt markiert.ProductDefaultVariant.originträgt die Unterscheidung. - Produkt-Tiles zeigen die Farb-Swatches ihres Produkts: Die Swatch-Reihe existierte, wurde aber nie gefüllt und war damit immer leer. Tiles lesen die Options-Achsen des Produkts jetzt direkt und laden für eine Liste nicht mehr jede Variante, was die Datenmenge eines Produkt-Grids im Browser reduziert. Ein ausverkauftes Produkt deaktiviert seinen In-den-Warenkorb-Button weiterhin; dieser Zustand kommt jetzt aus der Vorgabe-Variante des Produkts statt aus einer geladenen Varianten-Entität.
ProductTileBasic.sizeVariantsträgt einen Eintrag pro Wert auf der Größen-Achse des Produkts, jeder adressiert die Variante, die der Connector für diese Größe benennt, und ist deaktiviert, wenn keine kaufbare Variante sie trägt. - Web-Fonts sind konfigurierbar:
fontsam Modul@laioutr-app/uierreicht die gesamte@nuxt/fonts-Konfiguration —families,defaults,providersundpreload. Ein gesetzter Wert ersetzt die Vorgabe, Arrays eingeschlossen;falseüberspringt das Modul, für eine Storefront mit eigenen@font-face-Regeln. Fonts werden nur noch als woff2 ausgeliefert: Ein Provider beantwortet eine Anfrage pro User-Agent und gibt jede Format-Stufe zurück; die altenwoff-Faces tragen keineunicode-range, passen also auf jedes Zeichen, und stehen zuletzt — was einen modernen Browser dazu bringt, sie anstelle der woff2-Dateien zu laden. Ergänzend greiftfonts.defaults.subsetsjetzt überhaupt: Zuvor erreichte jedes Subset, das ein Provider zurückgab, das Stylesheet, und ein unbenutztes lud, sobald ein einziges Zeichen es beanspruchte — auf einer Storefront 36,7 KB Glyphen, die nie rendern, auf jeder Seite. - Slider und Videos hydrieren und laden spät:
CommonSwiperhydriert, sobald er in die Nähe des Viewports kommt, statt im ersten Hydration-Durchgang — Karussells weit unten auf einer Seite initialisieren Swiper beim Laden also nicht mehr. Das servergerenderte Markup ist unverändert: Ein Slider sieht vor seiner Hydration genauso aus, er antwortet nur noch nicht auf Pfeile, Bullets oder Autoplay.CommonSwiperEagerimportieren, um das vorige Verhalten zu bekommen. ÜberMediagerenderte Videos warten ebenfalls, bis sie in Viewport-Nähe sind, bevor sie laden; Videos unter einemMediaAboveTheFold-Provider sind ausgenommen, andernorts überschreibtvideoPreloades pro Video. Und ein Bild über dem Falz emittiert seinen Preload-Link jetzt mitfetchpriority="high"— das<img>trug den Hinweis schon, aber der Preload-Link startet den Abruf und trug keinen, sodass der Browser das LCP-Bild mit Standardpriorität holte. - Banner: Höhe, Hintergrundfarbe und zweidimensionale Ausrichtung: Alle drei CTA-Banner-Sektionen — Banner Basic, Banner Integrated und Banner Showcase — bekommen eine Sizing-Steuerung (feste Höhe, responsive Höhe oder Seitenverhältnis), um dem Banner eine bestimmte Höhe zu geben. Die Content-Ausrichtung von Banner Basic wird zweidimensional (vertikal plus horizontal, etwa
bottom-left), und Banner Integrated bekommt eine Vertikal-Ausrichtung, sodass Inhalt an jeder Kante sitzen kann — auch unten. Die Vertikal-Ausrichtung greift erst, wenn der Banner eine bestimmte Höhe hat. Dazu bekommen alle drei, in ihrer Sektions- wie in ihrer Block-Variante, eine Hintergrundfarbe für das innere Banner-Tile, unabhängig vom äußeren Backdrop der Sektion; Text- und Icon-Kontrast passen sich der gewählten Farbe automatisch an. Bestehende Sektionen rendern unverändert. Containerbekommt eine Tablet-Stufe:columnsTabletundgapTabletfügen eine Stufe zwischen Mobil und Desktop ein. Spaltenzahl und Gap lösen jetzt in drei Stufen auf — mobil (unter 600px), Tablet (600–1279px), Desktop (ab 1280px) —, ein Breakpoint-Schema, das mit dem Sizer geteilt wird. Beide fallen ungesetzt auf ihren Mobil-Wert zurück. Die Container-Sektion bekommt dafür die Steuerungen Tablet columns und Gap (Tablet).- Ganzflächig klickbare Banner und Hero-Slides:
HeroSlide,BannerBasic,BannerIntegratedundBannerShowcasenehmen einhrefan, das die ganze Komponente zu einem Link macht, gerendert als transparente ganzflächige Überlagerung. Es aktiviert sich nur, wenn kein CTA-Button vorhanden ist, sodass eine Komponente entweder button-getrieben oder ganzflächig klickbar ist — nie beides, was interaktive Elemente in einem Anker verschachteln würde. Die zugehörigen Blöcke und, ab 2.14.0, auchSectionBannerBasicbekommen dafür ein Link-Feld. - Neue Blöcke und Steuerungen für Studio:
BlockBasicTablebringt dieBasicTable-Molekül als Studio-Block, mit einem autorisierten Array aus Label/Wert-Paaren und deroutlined/plain-Variante als Design-Stil; es ist eigenständig und wird damit in jeder Sektion angeboten.BlockProductSlidermacht den Produkt-Slider als eigenständigen Block verfügbar, sodass er auch in einen Container-Slot passt — Konfiguration, Datenbindung und Rendering sind mitSectionProductSlideridentisch.ProductDetailButtonGroupundBlockProductDetailButtonGroupstapeln vollbreite Handlungsaufforderungen auf der Produktdetailseite. Die Blöcke Mega Menu, Mobile Menu (Shop) und Side-by-Side Menu bekommen eine Start Level-Steuerung, die den geholten Kategoriebaum um die konfigurierte Zahl an Ebenen absteigt — für das Überspringen eines synthetischen Upstream-Wurzelknotens, ohne Query oder Connector zu ändern.BlockPaginationversteckt sich, wenn die Ergebnismenge auf eine Seite passt, mit einemshowOnSinglePage-Schalter, um es zu behalten. UndBlockFooterMenu-Einträge bekommen eine Liste Visible in markets: leer heißt überall sichtbar, was jedes vorher autorisierte Menü tut. Die beiden Teile dahinter sind für andere Komponenten exportiert —visibleMarketsFieldfür das Schema undinMarket()für den Render. - Captions bekommen eine Text-Size-Einstellung, so verdrahtet wie Heading und Subline schon.
CaptionFlaghatte seine Größe fest verdrahtet; sie ist jetzt optional, der aus der Variante abgeleitete Wert bleibt der Rückfall. Die Optionsliste ist bewusst eine Teilmenge der geteilten Text-Größen-Skala: Die Caption-Utilities kommen aus den--caption-*-Tokens, die beixlenden,2xlund größer werden deshalb nicht angeboten statt angeboten und stillschweigend wirkungslos zu sein. Die Einstellung landet auf dem geteiltencaptionVariant-Style-Dekorator, jede Sektion und jeder Block, der ihn schon nutzt, bekommt die Steuerung also mit. MediaVideomeldet Meilensteine: Einmilestone-Ereignis feuert, wenn die Wiedergabe jedes Viertel des Videos überschreitet, und trägt{ milestone, currentTime, duration }, wobeimilestoneder erreichte Anteil ist —0.25,0.5,0.75oder1, je einmal pro montiertem Player.useMediaMilestones(playback, onMilestone, config?)ist der kopflose Detektor dahinter, für dasselbe Verhalten an einem nicht-nativen Player; er nimmt reaktivecurrentTimeundduration, weiß nichts über Medienelemente oder Analytics und akzeptiert{ fractions }anstelle der Viertel.SocialSharemeldet die gewählte Plattform: Es emittiertsharemit der Plattform, die ein Besucher gewählt hat, undBlockSocialSharemeldet das alsweb/share-Ereignis. Das Payload trägt alleinmethod— die Plattform-ID (facebook,x,linkedin,pinterest,email). Der Block bindet an keine Entität, geteilt wird also die Seite, und der Umgebungs-Seitenkontext legt deren URL, Pfad und Typ ohnehin auf jedes Ereignis. Jeder Button ist ein ausgehender Link auf den Dialog der Plattform, das Ereignis markiert also den Klick und keinen abgeschlossenen Teilen-Vorgang.- Login und Konto: Wer sich in den Produktbewertungen anmelden will, wird jetzt zu seinem Konto geschickt. Die Login-Aufforderung löst über die Aktion
ecommerce/auth/login-oauthauf und funktioniert damit mit jedem Connector, der sie beantwortet — einer OAuth-Autorisierungs-URL, wo Konten gehostet sind, oder einem Link auf die Konto-Seite, wo die Storefront ihre eigene rendert. Der Konto-Button und die Login-Aufforderung des Bewertungsbereichs leiten statt eines Popups auf den Kunden-Login um und tragen den aktuellen Pfad mit, sodass der Kunde dorthin zurückkommt. Dafür braucht es einen Connector, der diereturnTo-Eingabe vonAuthLoginOauthActionliest; gegen einen, der sie ignoriert, funktioniert der Login trotzdem. Das Login-Popup scheitert außerdem in Safari und Firefox nicht länger stillschweigend: Es wird geöffnet, während der Klick noch eine vertrauenswürdige Geste ist, und fällt auf eine ganzseitige Umleitung zurück, wo ein Popup ohnehin blockiert wird. Und der Warenkorb eines angemeldeten Kunden trägt jetzt seine Identität, sodass der Checkout authentifiziert öffnet und die entstehende Bestellung an sein Konto hängt. - Ein Link mit mehreren Optionswerten öffnet die richtige Variante: Eine Produktdetail-URL, die mehrere Optionswerte benennt, öffnet die Variante, die alle trägt. Zuvor genügte jeder einzelne passende Wert, sodass
?variant=Rot&variant=XLein rotes Produkt in der falschen Größe öffnen konnte. Eine URL, die einen Wert benennt, öffnet weiterhin die erste Variante, die ihn trägt. - Child-Categories-Link und Kategorie-Karten aus einer Datenquelle (2.8.6): Neu ist das
ChildCategoriesLink-Token (ecommerce/category/child-categories) in canonical-types. Der Shopify-Connector implementiert es, indem er das Navigationsmenü liest (neue OptioncategoryMenuHandle, Vorgabemain-menu), den Knoten der Quell-Collection findet und dessen Collection-Kinder als eingebetteteCategory-Entitäten zurückgibt, lokalisiert über@inContext.SectionCategoryCardSliderbekommt dazu eine optionale Kategorie-Query, deren Entitäten als Karten rendern; manuell gesetzte Slot-Karten haben weiterhin Vorrang. MenuSideBySidebekommt einenbelow-separator-Slot auf der Wurzelebene. Der Bereich unter dem Trenner des Wurzel-Drawers renderteMobileMenuListItems aus demrootMenuItems-Prop und nichts weiter, sodass eine Storefront, deren Schnellzugriffe gestaltete Buttons statt Listenzeilen sind, dort nichts unterbringen konnte. Der Slot wird auf jeder Ebene zwischen Konsument und Ziel weitergereicht, jeweils nur, wenn ein Konsument ihn tatsächlich befüllt.ProductListingGridnimmt einenproductTile-Scoped-Slot an, gebunden als{ product }, passend zu dem, denProductSliderschon anbietet; sein Rückfall ist das vorher gerenderte Tile.CartSummaryBoxemittiertcheckout, wenn seine Handlungsaufforderung geklickt wird, undCartSheetreicht es weiter — der Button behält seinhref, die Navigation ist also unverändert.CartSheetkann gesagt bekommen, wie viele Artikel seine Header-Pille melden soll, über das neue optionalecartContent.itemCount. Die Pille war fest auf die Zahl der Zeilen verdrahtet, was nur bei Menge 1 pro Zeile der Artikelzahl entspricht — eine Storefront, deren Header-Warenkorb-Badge Einheiten zählt, zeigte für denselben Warenkorb also zwei verschiedene Zahlen: Zwei Produkte mit Menge 3 lasen im Header 6 und in der Schublade 2. Ohne den Wert bleibt der Rückfall auf die Zeilenzahl.
Fehlerbehebungen & Verbesserungen
- Behoben:
QuantityPickerentfernte einen Artikel versehentlich. Es emittiertedelete, wenn das Mengenfeld am Minimum den Fokus verlor oder der Minus-Button dorthin heruntersprang — ein Klick in das Mengenfeld einer Warenkorbzeile mit Menge 1 entfernte die Zeile also.deletefeuert jetzt nur noch für den Löschen-Button am Minimum oder für einen darunter eingegebenen Wert. - Behoben: Die Produkt-Tiles in einem Produkt-Grid zeigen ihren In-den-Warenkorb-Button, Marke, Beschreibung, Flags und Bewertung wieder.
ConnectedProductTileBasicdeklarierte jeden Schalter als optionales Boolean ohne Vorgabe, sodass Vue einen ungesetzten auffalsewarf und die Komponente diesesfalseanProductTileBasicweiterreichte — womit es dessen eigenesdefault: trueschlug.BlockProductsListingübergibt keinen dieser Schalter, jedes Tile in einem Grid renderte also mit allen fünf ausgeschaltet. - Behoben: Ein Preis in einem Commerce-Ereignis wird für die Variante gebildet, die das Ereignis benennt, nicht für das Produkt, zu dem sie gehört. Der eigene Preis eines Produkts ist ein „ab"-Preis über seine Varianten, und die Projektion meldete schon SKU und Namen der aktiven Variante — beides mit dem Produktpreis zu paaren bepreiste also eine Variante als eine andere und verstand bei breiter Variantenspanne den Betrag um eine Größenordnung.
projectAnalyticsProductliest jetzt die Preise der aktiven Variante und fällt nur dort auf die des Produkts zurück, wo die Variante keine hat. Ein Variantenpreis trägt nie den Streichpreis des Produkts, weil das einen Rabatt erfinden würde. Deradd_to_cart-Wert eines Tiles wird aus der tatsächlich hinzugefügten Variante berechnet. - Behoben: Produkt-Tiles rendern weiter, wenn einer Variante die Components fehlen, die sie lesen. Ein Query-Ergebnis kann legitim eine Produktvariante tragen, deren Components nicht aufgelöst wurden; der geteilte Produkt-Tile-Mapper las
options.selectedundavailability.statusvon jeder Variante, sodass eine einzige solche Variante das ganze Produkt-Grid, den Produkt-Slider oder den Produkt-Slider – Showcase hinter dessen Error Boundary nahm. Solche Varianten werden jetzt übersprungen, und das Tile rendert mit einer kurzzeitig kürzeren Größenliste. - Behoben: Der In-den-Warenkorb-Button auf Tiles in der Sektion Produkt-Slider – Showcase tat beim Klicken nichts — kein Spinner, kein Toast, keine Warenkorb-Anfrage —, während derselbe Button im einfachen Produkt-Slider funktionierte. Beide verhalten sich jetzt gleich: Ladezustand pro Tile, Erfolgs- und Fehler-Toasts, aktualisierte Warenkorb-Zahl.
ProductSliderShowcasebekommt außerdem einen optionalenproductTile-Slot. - Behoben:
CategoryCardSliderundCategoryCardGridrendern ihrnodes-Prop und ihren Default-Slot zusammen,nodeszuerst. Zuvor ersetzte der Default-Slotnodesvollständig, und weil die Kategorie-Karten-Sektionen immer einen übergeben, rendertenodesdurch sie hindurch überhaupt nie — ein an eine Kategorie-Datenquelle gebundener Kategorie-Karten-Slider zeigte also einen leeren Slider. - Behoben: Das Filter-Off-Canvas-Akkordeon öffnete das falsche Panel.
FilterOffCanvasAccordionItemnutzteactiveValue(den Anzeigetext) als reka-AccordionItem-Identität, sodass jeder nicht ausgewählte Listen- und Bereichsfiltervalue=""teilte und ein Klick auf irgendeinen Trigger den ersten öffnete und die anderen überlappte. Das Item nimmt jetzt ein eigenes, eindeutigesvalue-Prop für die Identität. - Behoben: Header- und Schnellfilter-Reihe der Filterleiste richten sich auf großen Bildschirmen an den umgebenden Seiteninhalt aus. Ihr horizontales Padding war ein festes
--spacing-ml, schmaler als das Padding des Seiten-Containers, sodass beide Reihen ablgleicht innerhalb der Inhaltsspalte saßen. Sie lesen jetzt--container-padding. - Behoben:
BlockMedias Sizing-Einstellung gilt nur noch für das Bild, nicht für Bild plus Beschreibung.Sizerumschloss den ganzen Block, sodass ein Seitenverhältnis von 16/9 das Verhältnis von Bild und Bildunterschrift war: Das Bild verlor genau die Höhe, die die Unterschrift nahm, und eine längere Unterschrift schrumpfte es weiter.MediaPreviewnimmt jetzt ein optionalessizingund wendet es auf seine Medienbox an, mit der Beschreibung darunter und außerhalb der bemessenen Box. - Behoben: Ein Absturz auf einer Hero-Slide, die eine Caption zeigt.
SectionHeroSliderriefresolveCaptionVariantohne Import auf, und ui-app registriert kein Auto-Import-Verzeichnis, sodass das Bauen der Slide-Props einenReferenceErrorwarf. Nur eine Slide mit mindestens einer sichtbaren Caption erreichte den Aufruf, weshalb es latent blieb. - Behoben:
BrandHerozeigt eine Hintergrundfarbe, wenn kein Hintergrundbild gesetzt ist.fallbackColorwurde die ganze Zeit anMediaStageübergeben, aberfinalMediafiel bei leerembackgroundauf die dekorative Hero-SVG des Themes zurück — die Farbe war also hinter einem deckenden Bild gemalt. Eine ausdrückliche Farbe hat jetzt Vorrang; die Theme-Vorgabe gilt weiter, wenn keine Farbe gesetzt ist. Das Feld heißt inSectionBrandHerojetzt „Background Color". Außerdem wirdBrandHerosdescriptionzu Rich Text: Das Feld war eintextarea, nahm also nur getippte Texte — und auf einer Markenseite ist sein ganzer Zweck, die Beschreibung der Marke aus dem Katalog zu tragen. Alsrichtextbietet Studio eine Datenquelle darauf an; das Prop weitet sich aufstring | HtmlFragment, Aufrufer mit einem einfachen String funktionieren unverändert. - Behoben: Das gefüllte Herz-Icon (
essentials/heart-filled) renderte beisize="s"undsize="m"nicht. - Ergänzt: Der String
review.logInin allen sieben ausgelieferten Sprachen — der Button der Bewertungs-Login-Aufforderung war fest auf Englisch verdrahtet. - Der Produkt-Slider – Showcase setzt Show Product Flags jetzt standardmäßig auf an, passend zum normalen Produkt-Slider (nur neue Sektionen; bestehende Konfigurationen sind nicht betroffen). Und
productTileMappergibt einsizeVariants-Array aus ({ value, label, disabled }, nach Varianten-ID geschlüsselt,disabledfür ausverkaufte Größen); das Varianten-Limit der geteilten Query steigt dafür von 5 auf 30, sodass vollständige Größenläufe nicht länger abgeschnitten werden.
Hinweise für Entwickler (Breaking Changes)
AuthLoginOauthAction(ecommerce/auth/login-oauth) gibt keine nackteauthorizationUrlmehr zurück (2.11.0), sondern eine diskriminierte Union: die OAuth-Autorisierungs-URL, wenn der Kunde keine Sitzung hat, und einenLinkauf sein Konto, wenn er eine hat. Vor dem Lesen auftypeeinengen. Das Konto-Ziel ist einLinkstatt einer URL-Zeichenkette, das Frontend löst es also auf — Shopify gibt einen externen Link auf seine gehostete Konto-Seite zurück, ein Connector, dessen Konto eine Storefront-Seite ist, einenpageType-Link aus demselben Vertrag.Reviewmeldet die Absicht statt sie selbst zu behandeln (2.17.0): Es emittiertsubmitmit den Formulardaten, wenn das Bewertungsformular abgeschickt wird, undlogin, wenn der Besucher die Login-Aufforderung bestätigt. Der primäre Button der Login-Aufforderung öffnet keinen eigenen Bestätigungsdialog mehr — er emittiertlogin, und der Konsument entscheidet, wohin der Besucher geht. EinReviewohnelogin-Listener zeigt die Aufforderung, kann aber niemanden anmelden.CartListItemmarkiert eine Zeile, deren Ware nicht gekauft werden kann (2.16.0): Es nimmt ein neuesisSoldOut-Prop, rendert ein „Out of stock"-Badge und trägtdata-unavailablean seiner Wurzel für das Styling;ConnectedCartSheetsetzt es aus der Verfügbarkeits-Komponente des Warenkorb-Artikels. Eine eigene Locale muss den neuen Schlüsselcart.itemSoldOutliefern.SectionCategoryCardSliderpaddet sich nicht mehr selbst (2.14.0): Die Sektion überschrieb das geteiltepaddingFieldmitdefault: 's', sodass jede Instanz mit einem vertikalen Sektions-Padding startete, das niemand angefordert hatte — und eine Sektion, die sich selbst paddet, kann nicht bündig an ihre Nachbarin gesetzt werden. Verhaltensänderung: Bestehende Instanzen, die das Padding-Feld nie angefasst haben, verlieren dieses Padding. Ausdrücklich aufSsetzen, um den alten Look zu behalten. Sieben weitere Sektionen tragen dieselbedefault: 's'-Überschreibung und bleiben hier bewusst unangetastet.- Der Desktop-Gap von
Containerschaltet bei 1280px statt bei 800px (2.10.0): Container mit einem ausdrücklichen Desktop-Gap und ohne Tablet-Gap nutzen zwischen 800 und 1279px jetzt den Mobil-Gap, wo sie vorher den Desktop-Gap nutzten — dieselbe Breakpoint-Vereinheitlichung, die der Sizer schon anwendet. Das Spaltenverhalten ist nicht betroffen. BlockPaginationversteckt sich auf einseitigen Ergebnismengen (2.13.0). Verhaltensänderung: Storefronts, die die Steuerung auf einseitigen Listen zeigten, tun das nicht mehr.showOnSinglePagein der Rules-Gruppe des Blocks einschalten, um das alte Rendering zu behalten — lohnend dort, wo die Höhe einer Liste sich nicht ändern soll, während Filter das Ergebnis einengen, weil ein erscheinendes und verschwindendes Steuerelement alles darunter verschiebt.BrandHeromit Hintergrundfarbe und ohne Bild wechselt von der Theme-Grafik zu dieser Farbe (2.15.0). Das ist der gemeldete Fehler, aber die Änderung ist sichtbar — die Farbe leeren, um die Grafik zu behalten.- Jede Listen- und Slider-URL von Produkten mit abgeleiteter Vorgabe-Variante ändert sich (2.20.0), weil das
?variant=daraus verschwindet. Ausdrücklich geteilte Links bleiben unangetastet.
Cockpit (Studio) August 2026
Highlights
- Studio hält den geöffneten Stand in der URL: Geöffnete Seite, Auswahl und Vorschau stehen in der URL — ein Reload oder ein geteilter Link kehrt genau dorthin zurück. Ergänzend werden auch der gewählte Markt und die gewählte Sprache in der URL gehalten, sodass ein Reload oder ein geteilter Link denselben Markt und dieselbe Sprache öffnet statt der Projekt-Vorgabe.
- Märkte haben einen Status: Entwurf oder Aktiv, neue Märkte starten als Entwurf, und die Markt-Karten zeigen Entwurfs- und Standard-Badges. Ein Projekt kann seinen Standardmarkt aus seinen aktiven Märkten wählen, und der Selektor benennt den Rückfall, der ohne Wahl gilt.
- Sortierung im Query-Editor: Eine Sortierung auf einer Query ändert die Vorgabe-Reihenfolge ihrer Ergebnisse.
- Link-Felder aus einem Query-Ergebnis: Ein Link-Feld kann sein Ziel jetzt aus einem Query-Ergebnis nehmen, so wie Medienfelder das schon können.
- Abandoned-Props-Panel in den Studio Devtools: Ein neues Panel meldet gespeicherte Props, deren Feld eine Sektion oder ein Block nicht mehr definiert oder zu deren Wert das Feld nicht mehr passt — und kann sie entfernen.
- Redirects auf der Vercel-Edge: Ein Managed-Vercel-Deploy veröffentlicht seine Redirects auf der Vercel-Edge, die sie vor jeder Route im Deployment beantwortet — eine alte URL kostet damit keinen Funktionsaufruf mehr.
- Vorschau im Desktop-Rahmen: Die Vorschau öffnet jetzt im Desktop-Rahmen (1920) statt sich auf die Breite des Editor-Fensters zu strecken.
Fehlerbehebungen & Verbesserungen
- Behoben: Der Import einer Redirect-CSV mit mehr als 256 Zeilen funktioniert.
- Behoben: Projekte mit mehr als 1000 Redirects deployen jeden Redirect, exportieren die vollständige Liste als CSV und importieren sie ohne Duplikate wieder ein.
- Behoben: Das Bearbeiten von Limit, Label, URL-Alias und Eingaberegeln einer Query in Studio wird gespeichert.
- Behoben: Studio meldet einen abgelehnten Projekt-Secret und benennt das Projekt, das der Host ausliefert — samt Link auf dieses Projekt.
- Behoben: Das Leeren eines Zahlenfelds setzt es auf seine Vorgabe zurück, und ein auf 0 gesetztes Feld zeigt die 0.
- Behoben: Die Billing-Einstellungen sind immer erreichbar, das Billing-Portal lässt sich also öffnen, bevor eine Zahlungsmethode hinterlegt ist.
Die vollständigen Changelogs mit allen technischen Details findest du unter docs.laioutr.io/getting-started/changelogs.