html★'s swap strategies (inner, outer, prepend, append, etc.) expect HTML fragments. This guide covers how to structure your server to render them.
What is a Fragment?
A fragment is an HTML snippet without the document wrapper — no <!DOCTYPE>, no <html>, no <head>, no <body>. Just the content that goes inside a swap target.
Full page:
HTML
<!DOCTYPE html><html><head><title>Products</title></head><body> <nav>...</nav> <main id="content"> <div class="product-list"> <div class="product">Running Shoe - $49</div> </div> </main></body></html>
Fragment:
HTML
<div class="product-list"> <div class="product">Running Shoe - $49</div></div>
Two Approaches
Approach 1: Full Pages + data-select (Recommended)
The server always returns full pages. The client extracts what it needs:
HTML
<nav data-target="#content" data-select="#content" data-push> <a href="/products?category=shoes">Shoes</a></nav> <main id="content"> <!-- html★ replaces this --></main>
Advantages:
- No server changes needed
- Progressive enhancement works automatically (no-JS users get full pages)
- Simpler caching (one version per URL)
- Works with static sites
Approach 2: Server-Side Fragment Detection
The server detects html★ requests and returns only the fragment:
JAVASCRIPT
app.get('/products', async (req, res) => { const products = await db.getProducts(req.query) if (req.headers['x-requested-with'] === 'htmlstar') { res.set('Vary', 'X-Requested-With') return res.send(renderProductList(products)) } res.send(renderFullPage('products', { products }))})
Advantages:
- Less bandwidth (no layout HTML transferred)
- Faster parsing (smaller response)
- Server controls exactly what's sent
Fragment Architecture
TEXT
┌─────────────────────────────────────┐│ Route: GET /products?category=shoes ││ ││ Full page request: ││ → renderPage('products', data) ││ → Returns: <html>...<body>... ││ <product-list>...</product-list> ││ ...</body></html> ││ ││ html★ fragment request: ││ → renderFragment('product-list', ││ data) ││ → Returns: <product-list> ││ <product-card>...</product-card> ││ ...</product-list> ││ ││ JSON API request: ││ → Returns: { products: [...] } │└─────────────────────────────────────┘
Fragment Renderer Pattern
A reusable fragment renderer keeps your route handlers clean:
JAVASCRIPT
" html.replaceAll(`{{${key}}}`,="" val),="" )="" renderPage(pageName,="" layout="loadTemplate('layout')" content="renderFragment(pageName," layout.replace('{{content}}',="" content)="" }<="" code-block=""><code-block language="javascript" routes="" products.js="" import="" {="" renderFragment,="" renderPage="" }="" from="" '..="" server="" render.js'="" app.get('="" products',="" async (req,="" res)=">" const="" products="await" db.getProducts(req.query)="" data="{" products:="" products.map(renderProductCard).join('')="" if="" (req.headers['x-requested-with']="==" 'htmlstar')="" res.set('Vary',="" 'X-Requested-With')="" return="" res.send(renderFragment('product-list',="" data))="" res.send(renderPage('products',="" })<="" code-block=""><h2>When to Use Which</h2><table><thead><tr><th align="left">Scenario</th><th align="left">Recommendation</th></tr></thead><tbody><tr><td align="left">Static site or simple server</td><td align="left">Approach 1 (full pages + <code>data-select</code>)</td></tr><tr><td align="left">Bandwidth-sensitive (mobile, slow networks)</td><td align="left">Approach 2 (server fragments)</td></tr><tr><td align="left">CDN-heavy deployment</td><td align="left">Approach 1 (simpler caching)</td></tr><tr><td align="left">Real-time updates (SSE, polling)</td><td align="left">Approach 2 (fragments are natural)</td></tr><tr><td align="left">Getting started</td><td align="left">Always start with Approach 1</td></tr></tbody></table><blockquote><p><strong>Start simple.</strong> Approach 1 works for most projects. Upgrade to Approach 2 only when you have measurable performance needs.</p></blockquote>