Advanced

Live Content

Keep an already-built site showing current content, without rebuilding it.

The problem

If your site renders blog content at build time — generateStaticParams, output: "export", or any static host — that content is frozen the moment the build finishes. Fix a typo in the Vlozi dashboard and your live site keeps showing the old text until someone rebuilds.

Live content closes that gap in the browser. Each rendered surface carries the content version it was built from; on page view it asks Vlozi whether that version is still current, and only if the answer is no does it fetch and swap in fresh content.

The check is a single ~30-byte response, cached for 10 seconds by your reader's browser and at our edge, and it happens during idle time. In the overwhelmingly common case — nothing changed since your build — that's all it costs.

Turn it on

One line, in a server module your root layout imports:

// app/layout.tsx
import { setVloziLiveDefaults } from "@vlozi/blog/server";
 
setVloziLiveDefaults({
  apiKey: process.env.NEXT_PUBLIC_VLOZI_KEY!,
  enabled: true,
});

Every @vlozi/blog/server component in the app picks it up. Individual components can override:

<ServerBlogList client={vlozi} live={false} />          {/* opt out */}
<ServerBlogPost client={vlozi} slug={slug} live />      {/* opt in */}
<ServerBlogList client={vlozi} live={{ ttlMs: 30_000 }} /> {/* tune */}

Why it needs its own key

The check runs in your reader's browser, so the key is embedded in your HTML where anyone can read it. That's what publishable keys (pk_*) are for — they're read-only and can be locked to your domain.

The SDK will not fall back to the key your server client uses, and refuses any key that isn't pk_*. @vlozi/blog/server exists so credentials stay on the server; quietly publishing one would take that decision away from you.

Create a publishable key in Settings → API keys, and set its allowed domains to your site's origin.

NOTE

Live content is off by default. Turning it on adds ~3.5 KB of client JavaScript and puts that key in your HTML — two things worth choosing rather than inheriting. With it off, your pages render byte-for-byte as before and load no extra JavaScript at all.

What it does and doesn't cover

Change Covered
Edit a published post ✅ within ~30s
Unpublish or delete a post ✅ — the page switches to not-found and gets noindex
Rename a category or tag
Publish a brand-new post ❌ — see below
Search-engine indexing ❌ — crawlers read your built HTML

New posts need a rebuild. On a static export the page for a new post doesn't exist as a file, and no amount of client-side JavaScript can create one — the list will update and then link to a URL that 404s. Pair live content with a deploy hook (Dashboard → Blog → Settings → Site updates) so new posts trigger a rebuild.

SEO is unaffected. Search engines index the HTML your build produced. Live content updates what a human sees; it doesn't change what was crawled. If fresh HTML matters for you, use ISR with a webhook, or a deploy hook.

When it's automatically disabled

The feature declines to run rather than doing something surprising. It's off if:

  • No publishable key was supplied, or the key isn't pk_*
  • Your backend doesn't provide the content-version endpoint
  • You passed a renderItem, render or hrefFor prop — those are functions and can't cross into the browser, so a refresh would re-render the surface with default markup and visibly restyle your page. Use hrefPattern instead of hrefFor to keep both a custom link and live updates.

At runtime it also gives up quietly — and leaves your page exactly as rendered — when the reader is offline, the key has been rotated, the request is rate-limited, or anything else goes wrong. A stale page beats a broken one.

{/* hrefFor is a function — disables live refresh */}
<ServerBlogCategoryList client={vlozi} hrefFor={(c) => `/blog/category/${c.slug}`} />
 
{/* hrefPattern is a string — survives into the browser */}
<ServerBlogCategoryList client={vlozi} hrefPattern="/blog/category/{slug}" live />

Styling the swap

The wrapper carries data-vlozi-live, which is "initial" before a refresh and "fresh" after, so you can add your own transition:

[data-vlozi-live="fresh"] {
  animation: vlozi-fade 200ms ease-out;
}
 
@media (prefers-reduced-motion: reduce) {
  [data-vlozi-live="fresh"] { animation: none; }
}

The SDK doesn't animate the swap itself — that's a motion decision that belongs in your design, not ours. It does hold the container's height steady across the swap so nothing below it jumps.

Blog · AdvancedEdit on GitHub