Designed for tablet+. View on a tablet or larger screen for the intended layout.
SPEAKER NOTES MODE — press s to hide
BLOCK 4 · AI GATEWAY (PRODUCTION)
4:20 – 4:40 · 20 min · 5L / 15HO · Lab
TOPIC 4.9 / 10 · ★ LOAD-BEARING (VueConf payoff)
Nuxt integration lab
The boundary in code. Vue knows useFetch. The server route knows Bifrost. Bifrost knows
everything else. Wire it. Watch a 402 fire when the budget caps out.
★ LOAD-BEARING (VueConf audience payoff)
Slide 1 / 3 · What Vue knows
The boundary in one frame
VUE KNOWS │ GATEWAY KNOWS
─────────────────────────────┼─────────────────────────────
the gateway URL │ providers
the Virtual Key (Bearer) │ models + aliases
useFetch │ routing rules
│ budgets
│ semantic cache
│ prompt repository
│ guardrails (if enterprise)
│ audit logs (if enterprise)
Vue doesn't know about providers. Vue doesn't hold API keys.
Slide 2 / 3 · The starter app
Four files
nuxt-app/
├── nuxt.config.ts # runtimeConfig: bifrostUrl, workshopVk
├── pages/index.vue # chat UI — useFetch against /api/chat
├── server/api/chat.post.ts # server route → POST to Bifrost
└── .env # BIFROST_URL, WORKSHOP_VK
Slide 3 / 3 · Why through a server route
Don't call Bifrost from the browser
CORS — Bifrost doesn't ship browser-CORS by default
VK secrecy — if you call Bifrost from the browser, the VK is in the bundle
Server route is the right shape — useFetch → Nuxt server → Bifrost
The Nuxt server is your application's secret boundary
Speaker notes
Slide 1 — the boundary: "Vue knows three things. The gateway URL. The Virtual Key as a Bearer token. And useFetch. That's it. The gateway knows everything else — providers, models, routing, budgets, cache, prompts, guardrails. Each layer concerned with its own thing. That's the gateway pattern in one slide."
Slide 2: "Four files in the starter Nuxt app. nuxt.config.ts exposes bifrostUrl and workshopVk via runtimeConfig. pages/index.vue is the chat UI — useFetch against /api/chat. server/api/chat.post.ts is the server route that POSTs to Bifrost with the Bearer token. .env holds both values."
Slide 3 — why the server route: "You might wonder why we go through a Nuxt server route instead of calling Bifrost directly from the browser. Two reasons. CORS — Bifrost doesn't ship browser-CORS by default. And VK secrecy — if you call Bifrost from the browser, the Virtual Key is in your JavaScript bundle, which means anyone with DevTools can copy it. The server route is the right shape. The Nuxt server is your application's secret boundary."
Slide 1 / 3 · Three steps to wired
HO #7 — Wire Nuxt to Bifrost (10 min)
Confirm .env — BIFROST_URL=http://localhost:8080 + WORKSHOP_VK=<your VK>
Run cd nuxt-app && npm run dev — Nuxt boots on localhost:3000
Send a message in the UI — confirm reply renders
Check the gateway — Bifrost dashboard → Observability → LLM Logs → request attributed to your VK
Slide 2 / 3 · Reading chat.post.ts
What the server route does
// the workshop's actual shape
export default defineEventHandler(async (event) => {
const { prompt } = await readBody<{ prompt: string }>(event)
const result = await callBifrost({
model: 'anthropic/claude-sonnet-4-6',
messages: [{ role: 'user', content: prompt }],
max_tokens: 500,
})
return { reply: result.choices[0]?.message?.content ?? '' }
})
export async function callBifrost(body: ChatRequest) {
const { bifrostUrl, workshopVk } = useRuntimeConfig()
return await $fetch(`${bifrostUrl}/v1/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${workshopVk}`,
},
body,
})
}
Two files. chat.post.ts shapes the request; bifrost.ts hides the gateway. Vue never sees either.
Also lives in chat.post.ts (not shown above for slide brevity): the 402 → 403 translation — when Bifrost returns 402 Payment Required for a tripped budget, the server route surfaces it as a 403 with a reason: 'Budget cap exceeded...' so the UI's policy-warning branch lights up. (Beat 4.9.3 leans on this.)
Slide 3 / 3 · What success looks like
Success criteria
Nuxt UI shows a chat box · message types render
Bifrost dashboard → Observability → LLM Logs → new request appears
Request is attributed to workshop-vk-1
Speaker notes
Slide 1 — set the HO: "Ten minutes to wire it up. Confirm your .env has BIFROST_URL and WORKSHOP_VK. Run npm run dev in the nuxt-app directory. Nuxt boots on localhost:3000. Send a message in the UI. Check the Bifrost dashboard — your request appears in LLM Logs, attributed to your VK."
Slide 2 — walk the code: "Two files do the work. chat.post.ts reads the prompt from the request, calls callBifrost, returns the reply. bifrost.ts is the boundary — it reads bifrostUrl and workshopVk from runtimeConfig, hits the chat completions endpoint with Bearer auth. The boundary is in code: no provider keys in this file. No model logic. No routing logic. Just the gateway URL, the VK, and the message."
Slide 3 — success criteria: "Three things tell you it's working. The Nuxt UI renders a response. The Bifrost dashboard shows a new request in LLM Logs. The request is attributed to your VK — not to a generic key."
During HO: loop the room. Common stumbles below.
Demo piece — Attendees
Sequence:
# 1. confirm .env
cat nuxt-app/.env
# should show BIFROST_URL and WORKSHOP_VK
# 2. start Nuxt
cd nuxt-app
npm run dev
# Nuxt boots on localhost:3000
# 3. browser to localhost:3000, type a message, hit send
Then back to the Bifrost dashboard:
Observability → LLM Logs → confirm new request appears
Click the request → confirm Authorization attributes to workshop-vk-1
📸 Dry-run capture target: screenshot of LLM Logs showing the Nuxt request attributed to VK. Lives at assets/block4-llm-logs.png.
Common gotchas (in priority order):
401 from Bifrost → VK mis-copied. Re-check .env. Most common failure.
CORS error → attendee bypassed the server route. They're hitting Bifrost from the browser. Redirect them through /api/chat.
Port collision → Nuxt wants 3000, Bifrost wants 8080. Both should be free.
npm install not run → starter repo's package.json deps not installed. Run npm install once in nuxt-app/.
Slide 1 / 2 · Trip the budget
Make a 402 happen
Send messages rapidly until the $0.50 budget caps out
Bifrost returns 402 Payment Required
Your server route sees the 402, surfaces it as a 4xx to the UI
Vue UI doesn't have to know whether it's a budget cap, a guardrail, or a downstream outage
Slide 2 / 2 · Reset
Recovery
Wait for the 1h reset (workshop budget is small on purpose)
Or delete + recreate the budget in the dashboard
In production: alert on 402, auto-page, audit, dashboard
Speaker notes
Slide 1 — provoke the failure: "Now we trip the budget. Send messages rapidly until the gateway returns 402. You'll see Bifrost return 402 Payment Required. Your Nuxt server route catches the 4xx and surfaces it as a policy-blocked message in the UI. Your Vue UI doesn't have to know whether it was a budget cap, a guardrail block, or a downstream outage — it just gets a 4xx from the server route. That's the boundary doing its job."
Slide 2 — reset: "To recover, wait for the 1-hour reset or delete and recreate the budget. In production, you'd alert on 402, page oncall, audit the spike, and look at the dashboard. The 402 is signal, not failure."
Close of HO: "This is the boundary in code. Vue knows useFetch. The server route knows Bifrost. Bifrost knows providers, models, routing, caching, budgets, prompts. Each layer concerned with its own thing. That's the gateway pattern, top to bottom."
Demo piece — Attendees
Send messages rapidly in the UI (10–20 short prompts) until 402 surfaces.
Capture script (in workshop repo at scripts/capture-budget-cap-shape.sh) — for instructor projector if attendees can't trip it fast enough:
for i in {1..50}; do
curl -s -X POST "$BIFROST_URL/v1/chat/completions" \
-H "Authorization: Bearer $WORKSHOP_VK" \
-H "Content-Type: application/json" \
-d '{"model":"anthropic/claude-sonnet-4-6","messages":[{"role":"user","content":"Say something verbose."}],"max_tokens":500}' \
-o "/tmp/bifrost-call-$i.json" -w "%{http_code}\n"
done | tail -10
When 402 appears in the output, cat /tmp/bifrost-call-N.json for the matching N has the exact 402 JSON.
📸 Dry-run capture target: screenshot of Nuxt UI showing the policy-blocked warning. Lives at assets/block4-policy-blocked-ui.png.
📋 Dry-run text capture: exact 402 JSON shape. Save to Block 4 - 402 response sample.md and use in this beat's slide.
Triage note: Tier 1 cut drops the 402 trip (saves ~4 min). Lose the visceral budget-cap moment, but keep the wire-up.
← Topic 4.8 — Paywall Tour
Topic 4.9 of 10
Topic 4.10 — Recap + Q&A →