Visual feedback
Optional third-party feedback widget on Vercel deployments
You can show a visual bug-reporting widget (Feedbucket, Marker, and similar) on a deployed textbook without editing index.html. Static builds are unchanged: opening index.html in a browser (or copying dist/) never loads the widget.
tbx update overwrites index.html, so the snippet must not live there. Put it in a Vercel environment variable instead. When Vercel builds the book, a Vite plugin inserts the snippet before </body> — the same gating as password protection (VERCEL_ENV is production or preview). This is not Routing Middleware: middleware cannot rewrite HTML, so injection happens at build time.
Enable on Vercel
- Copy the widget HTML from the vendor (usually a
<script>tag). - In the textbook's Vercel project, add an environment variable:
- Name:
TBX_FEEDBACK_SNIPPET - Value: the full HTML snippet (multiline is fine)
- Name:
- Redeploy so the Vercel build picks up the value.
Do not prefix the variable with VITE_. That would embed the snippet in client JavaScript for every local build.
If the variable is unset or empty, nothing is injected.
Example: Feedbucket
Paste the Installation snippet from your Feedbucket project:
<script
type="text/javascript"
src="https://cdn.feedbucket.app/assets/feedbucket.js"
data-feedbucket="YOUR_PROJECT_KEY"
defer
></script>That entire tag is the env var value. Swap vendors later by changing the variable and redeploying — no book code changes.
Do not commit a real project key in a public repo unless you intend the widget to appear for every visitor. Feedbucket can also gate the widget with a secret query parameter — see Feedbucket's visibility docs.
Local development and static builds
The snippet is not injected in:
vite dev/vite previewnpm run buildoutput (dist/releaseand theONE_FILEbundle)- Opening
index.htmlfrom disk or any static host that is not Vercel
Even if you set TBX_FEEDBACK_SNIPPET in a local .env file, a local Vite build still skips it unless the process is a Vercel preview/production build.
Modals
Template modals treat third-party widgets as outside the dismiss layer, so clicking the widget does not close an open hotspot (or Notes / Help) dialog. Backdrop click, the close button, and Escape still close the modal.
Widgets that mount a custom element or iframe outside #root (the usual pattern) are recognized automatically.
Custom modals in a book
Import Modal from src/ui/library/Modal, not from @heroui/react, so isolation applies.
import { Modal } from "@/ui/library/Modal";Existing books pick up the plugin and wrapper with tbx update, then set the env var and redeploy.