You've built a Preact starter template with Vite, routing, and maybe a Firebase or Supabase backend. You push it to GitHub, write a README, and wait for someone to try it. They clone the repo, run npm install, then hit an error because they didn't set VITE_API_KEY. They open an issue asking how to deploy. You copy-paste commands. They deploy, but forgot the environment variables again.
Here's the fix: a deploy button that reads configuration from the repository, prompts the user for secrets, and deploys in one click. No cURL commands, no hunting through documentation for the right build directory. The platform reads a JSON file committed to the repo, pre-fills the deploy form, and asks for exactly the variables your app needs.
What a deploy button does
A user clicks a badge in your README. They land on a deploy screen that shows:
- The repository name and branch (pre-filled from the URL)
- The build command (read from
pandastack.jsonin the repo) - The output directory (also from
pandastack.json) - A form prompting for
VITE_API_KEYandVITE_SUPABASE_URL(defined inpandastack.json'senvarray)
They fill in two fields, click Deploy, and get a live URL thirty seconds later. No documentation reading, no missed environment variables, no "it works on my machine" reports.
Setting up pandastack.json
This file lives in your repository's root. The deploy screen reads it from GitHub's public contents API (no authentication required for public repos), base64-decodes it, and merges the values into the deploy form.
For a Preact + Vite app:
{
"type": "static",
"name": "preact-starter",
"language": "nodejs",
"buildCommand": "npm run build",
"outputDir": "dist",
"env": [
{
"key": "VITE_API_KEY",
"description": "API key for the backend service"
},
{
"key": "VITE_SUPABASE_URL",
"description": "Supabase project URL (https://xxx.supabase.co)"
},
{
"key": "NODE_ENV",
"value": "production"
}
]
}Field breakdown:
type:"static"for compiled sites (Vite, Next.js export, Astro),"container"for server-rendered apps.buildCommand: the command that compiles your app. Vite defaults tonpm run build, which outputs todist/.outputDir: where the compiled assets live. The build system uploads everything in this directory to the CDN.env: an array of environment variables. Each entry can be:
- A string ("NODE_ENV") — the deploy screen prompts for it with no description.
- An object with key and description — shows a text field with help text.
- An object with key, description, and value — pre-fills the field; the user can override it.
Variables starting with VITE_ are embedded into the bundle at build time. Vite replaces import.meta.env.VITE_API_KEY with the literal string "abc123" during compilation. This is different from server-side environment variables (like DATABASE_URL in a Node.js app), which are read at runtime. Vite environment variables must be set before the build runs.
Adding the deploy button to your README
# Preact Starter
A lightweight Preact app with Vite, preact-router, and Tailwind.
[](https://dashboard.pandastack.io/deploy?repo=yourorg/preact-starter)
## Quick Start
Click the deploy button above, or clone and run locally:
\`\`\`bash
npm install
npm run dev
\`\`\`The deploy URL follows this pattern:
https://dashboard.pandastack.io/deploy?repo=OWNER/REPOThe repo parameter is the only required field. Optional query parameters:
branch=dev— deploy a branch other thanmaintype=static— force static or container detectionbuildCmd=npm run build— override the build commandoutputDir=build— override the output directoryenv=API_KEY,SECRET_TOKEN— comma-separated list of environment variable names to prompt for
If pandastack.json exists, its values win over query parameters. This lets you set defaults in the repo and override them in the URL for special cases (e.g. a docs site that deploys from a docs/ branch).
How the deploy flow works
- 1User clicks the button and lands on
/deploy?repo=yourorg/preact-starter. - 2The platform fetches
https://api.github.com/repos/yourorg/preact-starter/contents/pandastack.json(public API, no auth needed). - 3It base64-decodes the file and parses the JSON.
- 4The deploy form pre-fills:
- Name: preact-starter
- Type: Static Site
- Build command: npm run build
- Output directory: dist
- Environment variables: two text fields for VITE_API_KEY and VITE_SUPABASE_URL, with NODE_ENV=production pre-filled.
- 1The user enters their API key and Supabase URL, clicks Deploy.
- 2The build runs:
npm install && npm run build, withVITE_API_KEYandVITE_SUPABASE_URLinjected as environment variables. - 3Vite compiles the app, replacing
import.meta.env.VITE_API_KEYwith the real key. - 4The
dist/directory is uploaded to the CDN. - 5The user gets a live URL:
https://preact-starter-abc123.pandastack.app.
Prompting for secrets without committing them
A common mistake: putting "value": "sk_live_xxx" in pandastack.json for a secret like an API key. This hard-codes the secret into the repo, which leaks it to anyone who clones the code.
Instead, define the key and description but omit value:
{
"env": [
{
"key": "VITE_STRIPE_PUBLIC_KEY",
"description": "Stripe publishable key (pk_test_... or pk_live_...)"
}
]
}The deploy screen shows an empty text field with the description as a label. The user fills it in, and the value is stored as an encrypted environment variable in the platform. It's injected at build time but never committed to the repo.
Monorepo support with rootDir
If your Preact app lives in a subdirectory (e.g. apps/web/), add rootDir:
{
"type": "static",
"name": "preact-web",
"rootDir": "apps/web",
"buildCommand": "npm run build",
"outputDir": "dist"
}The build system treats apps/web/ as the working directory. It runs npm install and npm run build from there, and uploads apps/web/dist/ to the CDN.
Debugging "Build succeeded but site is broken"
If the build completes but the deployed site shows a blank page or 404s for assets:
- 1Check the browser console. Look for 404s on
/assets/index-abc123.js. This meansoutputDiris wrong. - 2Verify the Vite build emits to
dist/(runnpm run buildlocally and check the directory). - 3If Vite outputs to
build/instead ofdist/, updateoutputDir: "build"inpandastack.json.
The second common issue: Vite's base path. If the app is deployed to a subdirectory (rare for PandaStack, common for GitHub Pages), you need base: '/' in vite.config.js. PandaStack serves every app from its own domain root, so base should always be '/'.
Comparing this to other platforms
Vercel: deploy buttons work the same way. If you have a Vercel button, you can swap the domain and it mostly works. PandaStack's pandastack.json is Vercel-compatible (same env array shape, same buildCommand field).
Netlify: uses netlify.toml instead of JSON. You can keep that file for Netlify deployments and add pandastack.json alongside it for PandaStack.
Render: uses a render.yaml. Same deal — add pandastack.json without removing the existing config.
The advantage of pandastack.json over query parameters: you version the configuration with the code. When you change the build command or add a new environment variable, you commit the updated JSON, and every deploy button click uses the new config. No updating links in the README.
Using the CLI instead
If you're deploying from a local machine or CI, the panda CLI reads the same pandastack.json:
panda login
panda projects create --repo yourorg/preact-starter --branch mainThe CLI reads pandastack.json from the repo, prompts for any environment variables that don't have a value, and deploys. Same outcome as the button, but scriptable.
For CI, use a psk_ token (project-scoped key):
export PANDASTACK_TOKEN=psk_live_xxx
panda projects deploy <project-id>The token bakes in the organization, so you don't need to pass --org. This is the right credential type for GitHub Actions or GitLab CI.
What you've built
A repository that anyone can deploy in one click, with environment variable prompts built into the deploy flow. No documentation page explaining how to set VITE_API_KEY. No issues asking "how do I deploy this?" The config lives in the repo, versioned alongside the code, and the deploy button reads it automatically.
PandaStack serves the compiled Preact app from a CDN with aggressive caching (hashed assets cached for a year, index.html revalidated). A redeploy purges the cache automatically, so users never see stale JavaScript. The app has zero idle cost — no server runs between requests.
If you're building a starter template, a side-project boilerplate, or an open-source tool, a deploy button is the difference between "read the docs and figure it out" and "click once and it works."
References
- [Vite environment variables](https://vitejs.dev/guide/env-and-mode.html)
- [Preact deployment guide](https://preactjs.com/guide/v10/getting-started/#deploying-preact)
- [PandaStack deploy button docs](https://docs.pandastack.io/projects/deploy-button/)
- [PandaStack static sites](https://docs.pandastack.io/projects/static/)