Bean & Bark: every PR gets a URL
Tunde's first storefront PR is up: the checkout page now shows the wholesale discount as its own line, so buyers can see what Friday's fix changed. Nadia wants to see it — not a diff, not a promise, the actual checkout page — and she's at the roastery with a phone. What a reviewer needs is a URL. What nobody needs is another permanent environment.
- Enable previews once in the manifest — and read what each setting means
- Publish a PR's front end beneath the dev site's distribution:
kanject aws preview publish <name> - Run the lifecycle:
preview ls,url,extend --by,rm/rm --expired - Say where a backend preview comes from (your CI, not the CLI) and what keeps real stages out of preview cleanup
The storefront, as a service
Tunde's storefront repository is a Kanject service too, beanandbark-storefront, with two entries: api, the Storefront API that took the Pricing SDK candidate on Monday (a .NET Lambda that prices carts), and web, the checkout front end — a Vite app deployed as a static site behind CloudFront:
{ "id": "web", "projectPath": "web", "deployTarget": "static-site", "renderMode": "spa", "build": { "kind": "static", "mode": "vite", "command": "npm run build" }, "coupledTo": "api"} coupledTo pairs the two: they deploy and roll back together, and the site's /api/* requests reach the API through the same CloudFront distribution. kanject aws deploy --env dev has been shipping both since Tunde started.
Enable it once
Previews are off by default. Turning them on is a manifest block, committed like everything else:
"preview": { "enabled": true, "basedOn": "dev", "ttlDays": 7, "stackName": "preview-{name}-{service}"} Read it as policy, not just config. basedOn: "dev" names the deployed stage whose static site hosts the previews — its bucket and CloudFront distribution — and whose region, profile, and Parameter Store path they inherit. ttlDays: 7 gives every preview an expiry date. stackName is where publish looks for a PR's backend, if one exists (more on that below).
Publish the PR
# Publish the PR's checkout page beneath the dev site's distributionkanject aws preview publish pr-12 ⚠ Backend preview stack 'preview-pr-12-beanandbark-storefront' isn't up —publishing the front-end without previewApiUrl. ● Building 'web' for /previews/pr-12/ … ✓ Preview pr-12 live at https://d1q2w3e4r5t6y7.cloudfront.net/previews/pr-12/ The CLI reads the dev site's stack outputs, builds web (npm ci, then the Vite build) with a base path inside /previews/pr-12/, uploads it, updates the routing, records the expiry in the dev stage's ledger, and invalidates that path on CloudFront. Dev's own assets don't move. It needs the dev site already deployed, and Node and npm on the machine that runs it.
The warning is expected. Tunde's PR only touches the page, so there is no backend preview, and the preview's /api/* requests go to dev's Storefront API — which already runs the released Pricing SDK. Nadia opens the link on her phone and sees the new discount line over real dev prices.
The lifecycle
kanject aws preview ls # what's alive, and when it expireskanject aws preview url pr-12 # print the live URL againkanject aws preview extend pr-12 --by 7dkanject aws preview rm pr-12 # done reviewing? take it downkanject aws preview rm --expired # or let CI sweep the stragglers ── Previews beanandbark-storefront ────────────────────────────────────────────╭───────┬────────┬────────┬───────────────────────────────╮│ Name │ Handle │ Status │ Expires │├───────┼────────┼────────┼───────────────────────────────┤│ pr-12 │ pr-12 │ active │ 2026-07-23 14:10 UTC (in 6d) │╰───────┴────────┴────────┴───────────────────────────────╯ Every lifecycle command reads live state from AWS: the preview's record in the dev stage's artifact bucket, and the dev site's stack outputs for its CloudFront domain. preview url prints the address again only while the preview is active, so a CI job that comments the link on the pull request needs AWS credentials with read access.
Nobody remembers to clean up — so nobody has to
The PR merges Friday morning; the preview is now garbage. Maybe you rm it in the merge ritual — but the honest answer is that reviewers forget, which is why every preview carries its TTL. A scheduled preview rm --expired in CI sweeps everything past its date: the "three temporary stages from last year" problem, structurally deleted. Need more time on a genuinely long review? That's what extend --by 7d is for — a deliberate decision to keep one preview alive for seven more days from now, instead of immortality by default.
- Previews are enabled by a committed manifest block:
basedOnpicks the deployed stage whose static site hosts them, andttlDaysgives every preview an expiry. preview publish <name>builds the static-site entry under/previews/<name>/on that stage's CloudFront distribution; dev's assets never move.- No command creates a Lambda or ECS preview: a PR's backend preview is a stack your CI deploys under
stackName, andpublishwires the page to it when it exists. url,extend --byandrmread live state; tags and ledger records keep real stages outside the lifecycle, andpreview rm --expiredsweeps whatever outlived its TTL.
kanject aws preview url pr-12 to comment the link on the PR. What AWS permissions does that step need?kanject aws preview extend pr-12 --by 7d. What changes?preview rm; the safety boundary is the preview inventory, not a name pattern.Show solution
kanject aws preview rm --expired removes every preview past its recorded expiry. It can't touch dev: static-site inventory is the preview records in the ledger, and stack inventory requires the kanject:preview tag.kanject aws preview rm --expired ttlDays controls eligibility.Show solution
kanject aws preview publish pr-31 uploads beneath /previews/pr-31/. Its recorded expiry is seven days after publishing, after which the next preview rm --expired removes it.