Embed the widget
Versioning & cache busting
YOUR-AGENT-ID.
The widget bundle ships at /w/widget.js and is
re-built every release. To avoid stale browser / CDN caches on
buyer sites, each build also publishes a
content-hashed twin at
/widget/widget.<hash>.js plus a manifest at
/w/manifest.json.
Snippets from before the neutral path keep working:
/widget/widget.js answers with a permanent redirect (301)
to /w/widget.js, with the query string intact, so
?v= and the tag-manager ?agent= still
arrive. The checkout return pages redirect the same way, and the old
/widget/manifest.json still answers as it always did.
Default embed (auto-update)
The snippet the admin sees on /app/agents/{id}
points at the stable /w/widget.js with a
?v= cache hint. The route always streams the bundle named
in the manifest, so each release reaches the page without anyone
touching their HTML.
<script async src="https://blengi.com/w/widget.js?v=ac900edd33be" data-agent-id="YOUR-AGENT-ID"></script>
Manifest endpoint
Buyers who want to programmatically fetch the current bundle can
hit GET /w/manifest.json:
{
"version": "2.0.0",
"hash": "abc123abc123",
"file": "widget.abc123abc123.js",
"url": "/widget/widget.abc123abc123.js",
"generated_at": "2026-05-16T07:11:36Z"
}
The endpoint sets
Cache-Control: public, max-age=60, must-revalidate
so the manifest itself never holds long, but the bundle file can
be served with long-lived Cache-Control headers
because the URL changes per release.
Bundle caching & startup speed
The unhashed /w/widget.js that embed snippets load
is served with
Cache-Control: public, max-age=300, stale-while-revalidate=86400.
Repeat page views start the widget straight from the browser cache —
no blocking round-trip before the script runs — and the browser
refreshes the bundle in the background after the five-minute window.
A new release therefore reaches all visitors within at most five
minutes of deploy.
The widget also caches the agent's chrome (theme, copy, greeting, starter questions) in the visitor's browser after each successful boot. On the next page view the bar paints immediately with that real, localized chrome and becomes interactive as soon as the fresh init round-trip completes — the visitor never stares at an empty corner. Tokens and conversation state are never cached this way; they always come from the fresh init.
Pinning to a specific version
If you need to lock the bundle on the buyer's side (e.g. to
coordinate a marketing campaign), copy the
file field from the manifest and use it verbatim:
<script async src="https://app.example.com/widget/widget.abc123abc123.js"></script>
Each build replaces the previous hashed file, so a pinned hash only
lasts until the next release: pin for a short campaign window, then
go back to /w/widget.js.
Fallback
If the manifest is missing (for example a partial deploy), the
snippet falls back to
/w/widget.js?v=<md5-prefix> so buyers
never get an empty src. We keep the unhashed
widget.js as the canonical artifact too.
Build pipeline
npm run build:widget runs the Vite build, then
invokes bin/widget-postbuild.cjs which:
- Reads
public/widget/widget.js. - Computes a 12-char SHA-256 prefix.
- Copies the bundle to
public/widget/widget.<hash>.js. - Writes
public/widget/manifest.json.
Both artifacts and the manifest are committed to the repo —
the host doesn't run npm run build, the repo
is the deployment artifact.