Back

Automatische Skeleton-Screen-Generierung mit boneyard

Automatische Skeleton-Screen-Generierung mit boneyard

Skeleton-Loader gehören zu den UI-Patterns, die einfach aussehen – bis man sie tatsächlich umsetzt. Man misst DOM-Elemente aus, codiert Höhen hart, baut bedingtes Rendering ein – und dann ändert der Designer das Card-Layout, und das Spiel beginnt von vorn. Die Wartungskosten summieren sich unbemerkt.

boneyard-js verfolgt einen anderen Ansatz: Statt Skeleton-UIs von Hand zu schreiben, extrahiert das Tool Layout-Daten direkt aus den gerenderten Komponenten zur Entwicklungszeit und generiert die Platzhalter-Definitionen automatisch.

Wichtigste Erkenntnisse

  • boneyard-js generiert Skeleton-Loader automatisch, indem es Layout-Daten aus den realen Komponenten erfasst – damit entfällt die Notwendigkeit, zwei parallele Repräsentationen derselben UI zu pflegen.
  • Das DOM-Scanning erfolgt zur Entwicklungszeit über Playwright und erzeugt statische .bones.json-Dateien. Im Produktionsbetrieb findet kein Laufzeit-DOM-Traversal statt.
  • Die Erfassung läuft standardmäßig bei drei Viewport-Breiten (375, 768, 1280 Pixel), wobei horizontale Werte als Prozentangaben für responsives Verhalten gespeichert werden.
  • Das fixture-Prop und das --wait-Flag handhaben Komponenten, die von asynchronen Daten abhängen, welche während der Erfassung nicht verfügbar sind.
  • Ein Vite-Plugin hält die Bones bei jedem HMR-Update automatisch synchron und macht einen separaten CLI-Schritt überflüssig.
  • Adapter existieren für React, Vue, Svelte 5, Angular, Preact und React Native – alle teilen sich dasselbe Core-Format.

Das Problem manueller Skeleton-Loader

Die meisten Skeleton-Loader-Implementierungen sind von der eigentlichen UI entkoppelt. Man baut zunächst die tatsächliche Komponente und anschließend separat ein Skeleton, das deren Form annähert. Ändert sich die Komponente, driftet das Skeleton ab. Mit der Zeit passen die Ladezustände nicht mehr zum Inhalt, was zu Layout-Verschiebungen und visuellen Inkonsistenzen führt.

Bibliotheken wie react-loading-skeleton reduzieren zwar Boilerplate, erfordern aber weiterhin, dass man die Struktur manuell beschreibt. Man pflegt also nach wie vor zwei Repräsentationen derselben Komponente.

Wie boneyard-js Skeleton-Screens automatisch generiert

boneyard-js dreht den Workflow um. Man umschließt die reale Komponente mit einem <Skeleton>-Tag, führt einen CLI-Befehl aus, und das Tool erfasst das Layout automatisch.

So sieht das in React aus:

import { Skeleton } from 'boneyard-js/react'

function ActivityPanel() {
  const { data, isLoading } = useFetch('/api/activity')

  return (
    <Skeleton name="activity" loading={isLoading}>
      {data && <ActivityContent data={data} />}
    </Skeleton>
  )
}

Anschließend, mit laufendem Dev-Server:

npx boneyard-js build

Die CLI öffnet einen Headless-Browser über Playwright, ruft die App auf, findet jedes <Skeleton name="...">-Element und durchläuft den DOM-Baum darin. Sie verwendet getBoundingClientRect() auf Blattknoten – Textelementen, Bildern, Buttons, Formulareingaben – und erfasst deren Position und Größe relativ zum Skeleton-Root. Horizontale Werte werden als Prozentangaben für responsives Verhalten gespeichert, vertikale Werte hingegen in Pixeln. Der Border-Radius wird automatisch erkannt.

Dieser Prozess läuft standardmäßig bei drei Viewport-Breiten (375, 768, 1280 Pixel) ab und erzeugt pro Komponente eine .bones.json-Datei:

{
  "breakpoints": {
    "375": {
      "bones": [
        { "x": 0, "y": 32, "w": 43.59, "h": 34, "r": 8 },
        { "x": 43.59, "y": 39, "w": 23.76, "h": 33, "r": 999 }
      ]
    }
  }
}

Zusätzlich wird eine Registry-Datei (registry.js oder registry.ts) generiert. Importiert man diese einmal am Einstiegspunkt der App, wird jede Skeleton-Definition global verfügbar:

import './bones/registry'

Zur Laufzeit liest die <Skeleton>-Komponente die registrierten Bone-Daten zu ihrem name, rendert das passende Layout als absolut positionierte Rechtecke und tauscht diese gegen den realen Inhalt aus, sobald loading auf false wechselt.

Build-Time-Erfassung, kein Laufzeit-Scanning

Eine wichtige Unterscheidung: Das DOM-Scanning erfolgt während der Entwicklung, nicht in der Produktion. Die .bones.json-Dateien sind statische Artefakte, die zusammen mit dem Quellcode eingecheckt werden. Zur Laufzeit liest boneyard-js lediglich diese vorab generierten Definitionen. Es findet kein Live-DOM-Traversal im Browser statt.

Für React Native unterscheidet sich der Erfassungsmechanismus. Die <Skeleton>-Komponente scannt im Dev-Modus den Fiber-Baum mithilfe von UIManager, misst native Views und sendet diese Daten an die CLI. In Produktions-Builds wird dieser Scanning-Code vollständig ausgeschlossen.

Umgang mit dynamischen Inhalten und das fixture-Prop

Wenn eine Komponente auf eine API angewiesen ist, die während der CLI-Erfassung nicht verfügbar ist, kann das Skeleton fehlerhaft generiert werden, weil der Inhaltsbereich leer ist. Zwei Optionen lösen dieses Problem:

--wait verwenden, um die Erfassung nach dem Laden der Seite zu verzögern:

npx boneyard-js build --wait 2000

Das fixture-Prop verwenden, um statische Mock-Daten speziell für die Erfassung bereitzustellen:

<Skeleton
  name="activity"
  loading={isLoading}
  fixture={<ActivityContent data={mockData} />}
>
  {data && <ActivityContent data={data} />}
</Skeleton>

Der fixture-Inhalt wird ausschließlich während der CLI-Erfassung gerendert und hat in der Produktion keinerlei Auswirkung.

Achtung: Wenn die Komponente nichts rendert, solange data undefined ist, kollabiert das Wrapper-Element auf null Höhe und das Skeleton wird nicht angezeigt. Setze in diesem Fall ein minHeight auf das <Skeleton>, um dies zu verhindern.

Vite-Plugin für engere Integration

Für Vite-basierte Projekte lässt sich das separate CLI-Terminal komplett umgehen:

// vite.config.ts
import { defineConfig } from 'vite'
import { boneyardPlugin } from 'boneyard-js/vite'

export default defineConfig({
  plugins: [boneyardPlugin()]
})

Die Bones werden beim Start des Dev-Servers erfasst und bei jedem HMR-Update automatisch neu erfasst. So bleiben deine Build-Time-Skeleton-Loader ohne manuelles Eingreifen synchron mit der UI.

Framework-Unterstützung

boneyard-js liefert framework-spezifische Adapter als separate Paket-Exports aus:

FrameworkImport
Reactboneyard-js/react
Vueboneyard-js/vue
Svelte 5boneyard-js/svelte
Angularboneyard-js/angular
Preactboneyard-js/preact
React Nativeboneyard-js/native

Die Kern-Extraktionslogik und das .bones.json-Format werden über alle Adapter hinweg geteilt.

Lohnt sich der Einsatz von boneyard-js?

boneyard-js ist ein relativ junges Tool, weshalb mit einer weiterentwickelnden API zu rechnen ist. Das Kernkonzept ist jedoch tragfähig: Skeleton-UIs aus realen Layout-Daten zu generieren, statt sie von Hand zu pflegen.

Der praktische Nutzen zeigt sich am deutlichsten in Projekten mit häufigen Komponentenänderungen. Statt nach jeder Design-Iteration die Skeleton-Platzhalter zu aktualisieren, führt man einen einzigen Befehl erneut aus. Die .bones.json-Dateien aktualisieren sich, und die Frontend-Ladezustände bleiben akkurat.

Wer ohnehin Zeit damit verbringt, Skeleton-Loader mit der tatsächlichen UI synchron zu halten, für den lohnt sich der Einrichtungsaufwand der Automatisierung.

Fazit

Skeleton-Loader sollten nicht von den Komponenten abdriften, die sie repräsentieren – doch bei manuellen Implementierungen passiert genau das fast immer. boneyard-js löst dieses Problem, indem es Skeletons als generiertes Artefakt behandelt statt als handgeschriebenes. Der Erfassungsschritt läuft in der Entwicklung, das Ergebnis ist eine statische JSON-Datei, und die Laufzeitkosten sind minimal. Für Teams, die schnell an der UI iterieren, spart dieser Workflow echte Zeit und sorgt dafür, dass Ladezustände visuell den zugrunde liegenden Komponenten treu bleiben.

FAQ

In der Produktion findet kein DOM-Scanning statt. Die CLI bzw. das Vite-Plugin generiert zur Entwicklungszeit statische .bones.json-Dateien. In der Produktion liest die Skeleton-Komponente lediglich diese Definitionen und rendert absolut positionierte Rechtecke – das verursacht nur minimale Laufzeitkosten.

Es erfasst Bones standardmäßig bei drei Viewport-Breiten: 375, 768 und 1280 Pixel. Horizontale Positionen und Breiten werden als Prozentangaben gespeichert, sodass das Skeleton fließend zwischen Breakpoints skaliert, während vertikale Werte in Pixeln verbleiben, um vorhersagbare Abstände zu gewährleisten. Zur Laufzeit wählt das System anhand der aktuellen Viewport-Breite den am besten passenden Breakpoint aus.

Es gibt zwei Optionen. Das --wait-Flag verzögert die Erfassung nach dem Laden der Seite, um asynchronen Anfragen Zeit zur Auflösung zu geben. Alternativ akzeptiert das fixture-Prop eine Mock-Version der Komponente mit statischen Daten, die ausschließlich während der CLI-Erfassung gerendert wird. Beide Ansätze sorgen dafür, dass das Skeleton ein gefülltes Layout statt eines leeren Containers widerspiegelt.

Ja, und das solltest du auch tun. Die .bones.json-Dateien und registry.js sind statische Artefakte, die deine Skeleton-Layouts beschreiben. Sie einzuchecken hält dein Team auf demselben Stand, macht Builds reproduzierbar, ohne den Erfassungsschritt in der CI ausführen zu müssen, und ermöglicht es dem Code-Review, unerwartete Layout-Änderungen zusammen mit den auslösenden Komponenten-Edits zu erkennen.

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay