Concepts

Choosing the rendering boundary

What is rendered on the server, what runs in the browser and how to choose the boundary between them.

Do not default to frontend or backend rendering. Choose the smallest, clearest solution by weighing maintainability, lines of code, duplicated representations, performance, accessibility, SEO, and where the state actually changes. Alpine.js is an interaction layer, not a reason to move existing content and markup into JavaScript.

Keep content server-rendered when the browser only needs to show, hide, sort, or filter existing elements. Expose only the small values Alpine needs through data-* attributes:

<article data-year='[#fmt [#get item.date#] type=date fmt="Y"#]'
    x-show="year === 'all' || year === $el.dataset.year">
    [#get-html item.body#]
</article>

Use [#fmt var=data.records json#] when the client genuinely needs structured records for reactive creation, transformation, editing, or state changes:

<div x-data='{ records: [#fmt var=data.articles json empty=[]#] }'>
    <template x-for="item in records" :key="item.uuid">
        <div x-text="item.title"></div>
    </template>
</div>

For hybrid interfaces, server-render reusable markup inside a native <template> and let Alpine clone or populate it. This keeps the HTML structure in templates instead of duplicating it in JavaScript while still allowing client-side creation:

<template id="result-card">
    <article class="result-card">
        <h2 data-result-title></h2>
    </article>
</template>

For large, paginated, permission-sensitive, expensive, or frequently changing datasets, do not preload every record into the page. Let Alpine request the required page or slice through the API and keep pagination, loading, empty, and error states explicit. This reduces initial page weight and server work, and avoids sending records the user has not requested. Do not introduce API loading for a small static list when server rendering is simpler and faster.

Choose among the four approaches deliberately:

Approach Prefer it when
Server-rendered elements plus data-* Existing content only needs filtering, sorting, toggling, or selection
Server-rendered <template> plus Alpine The client creates elements dynamically but their markup should remain template-owned
Alpine plus [#fmt ... json#] The client genuinely needs structured data for reactive rendering or transformation
Alpine plus paginated API requests The dataset is too large, expensive, sensitive, or dynamic to preload responsibly

Prefer the option with the least duplicated data, markup, and logic. Passing already-loaded data to JSON is computationally cheap, but it can still add architectural complexity, duplicate server-rendered content, increase page weight, and move markup ownership into JavaScript. Count indirection and multiple representations as costs even when they require few lines of code.

View this page as Markdown