Static Hosting
Static application hosting
git clone https://github.com/bijectionhq/static-hosting.git
cd static-hosting
git checkout 32688ca55039d4af2058238aa8442065a1d7b863Bijection repository access is required. Request access.
Description
Static application hosting
Explore the package README for API concepts, configuration, and examples. Check the Bijection source for adaptation-specific setup.
Review the source and its dependencies before adopting this package. Source availability does not establish npm publication or runtime qualification.
Getting started
- Get the source at the revision above and read its package documentation.
- Check the required Bijection SDK version, dependencies, and external service credentials.
- Follow the package setup instructions and the guide to using components.
- Exercise the component in your deployment according to your project’s verification rules.
Open source
Maintained source: bijectionhq/static-hosting. Based on convex/static-hosting. License: Apache-2.0. Consult the repository for license terms, notices, and contributor attribution.
From the upstream publisher
A static hosting component for Convex.
Explore the demo ↗Demo video
Upstream overview
From the upstream package documentation.
Static-Hosting enables developers to serve React/Vite apps directly from Convex without external hosting providers. It uses Convex HTTP actions to serve files from Convex storage, with automatic SPA fallback for client-side routing, smart caching for hashed assets, and garbage collection of old deployments. The upload API uses internal functions for security, ensuring only authenticated CLI users can deploy files.
Use cases
From the upstream package documentation.
- Deploy full-stack apps with single backend: Host your React frontend and Convex backend functions in one deployment, eliminating the need for separate hosting services like Vercel or Netlify
- Prototype and demo apps quickly: Skip external hosting setup during development and share working demos directly from your Convex deployment URL
- Build internal tools and admin dashboards: Serve company-internal React apps from Convex with built-in authentication integration and no external dependencies
- Create self-contained SaaS applications: Package your entire application stack into Convex components that include both backend logic and frontend assets
- Implement live deployment notifications: Use the deployment query hooks to show users when new versions are available and prompt for page refreshes
How it works
From the upstream package documentation.
The component integrates into your Convex app configuration and HTTP router using registerStaticRoutes() to serve files from Convex storage. During deployment, the CLI uploads your built assets to Convex storage via internal functions like generateUploadUrl() and recordAsset(), which are only accessible through authenticated npx convex run commands for security.
The HTTP actions serve files with intelligent caching - hashed assets from bundlers like Vite get long-term cache headers while HTML files use ETags for freshness. The spaFallback option automatically serves index.html for unmatched routes, enabling client-side routing in single-page applications.
Deployment uses either a one-shot deploy command that builds your frontend with the correct production VITE_CONVEX_URL, deploys the Convex backend, then uploads static files, or separate upload commands for more control. The component automatically garbage collects old deployment files to prevent storage bloat.
Upstream package FAQ
Does the static-hosting component work with client-side routing?
Yes, @convex-dev/static-hosting automatically handles SPA routing by falling back to index.html when a requested file isn't found in storage. This ensures client-side routes work correctly with React Router, Vue Router, and similar libraries.
How does caching work with the static-hosting component?
The @convex-dev/static-hosting component implements smart caching where hashed assets (like bundle.abc123.js) get long-term cache headers, while HTML files are always served fresh with ETag support. This optimizes performance while ensuring users get the latest app version.
Is the upload API publicly accessible?
No, the @convex-dev/static-hosting component uses internal Convex functions for uploads, making them secure by default. Only your deployment process can upload files, not external clients or unauthorized users.
What bundlers does the static-hosting component support?
The @convex-dev/static-hosting component works with any bundler that generates static files, including Vite, Create React App, Next.js static export, Parcel, and Webpack. It requires zero configuration and handles standard build outputs automatically.
Use with agents and CLI
Give your coding agent the component’s source identity and setup guidance.
Help me integrate Static Hosting with Bijection.
Read: https://bijection.com/components/static-hosting/SKILL.md
Check SDK compatibility and dependency availability.
Follow this project's execution and verification rules.Upstream README.md
View repository ↗Documentation from convex/static-hosting, recorded October 23, 2026. APIs and setup may differ in the Bijection adaptation.
Convex Static Hosting
A Convex component for hosting static React/Vite apps directly on Convex: no
separate hosting provider, no DNS to wire up, no second deploy target. Run one
command and your frontend is live at https://<deployment>.convex.site
alongside your backend.
Features
- 🚀 One-command deploy: build, push backend, and upload static files in a single step.
- 🔄 SPA routing: paths without an extension fall back to
index.html. - ⚡ Smart caching: static files are cached for speed, with safe updates when a new version is deployed.
- 🔔 Deployment update notifications: show connected users a prompt when a new version is ready.
- 🔒 Authenticated uploads: uploads go through the Convex CLI's authenticated session; there's no public upload endpoint.
- 🧹 Automatic cleanup: files from previous 0.2.x deployments are garbage collected on every deploy. The migration guide covers one-time v1 cleanup.
https://github.com/user-attachments/assets/5eaf781f-87da-4292-9f96-38070c86cd39
Quick Start
Upgrading an existing 0.1.x app? This is not a package-only update. Point your coding agent at the 0.1.x to 0.2.x migration guide before it changes anything. The guide covers the storage-breaking re-upload, preserving auth and webhook URLs, historical v1 blob auditing, deployment sequencing, and verification.
npm install @convex-dev/static-hosting
npx @convex-dev/static-hosting setup
For a new setup, the command adds the component to convex/convex.config.ts and
creates a deploy script in package.json. It does not overwrite an existing
Convex config or deploy script. Complete any manual edits printed by the
command, and confirm that npm run deploy invokes this package before running:
npm run deploy
Your app is live at https://<deployment>.convex.site.
Setup
1. Install
npm install @convex-dev/static-hosting
2. Register the component
convex/convex.config.ts:
import { defineApp } from "convex/server";
import staticHosting from "@convex-dev/static-hosting/convex.config";
// Your own HTTP endpoints (convex/http.ts) are served under /api so the
// static site can own the root.
const app = defineApp({ httpPrefix: "/api" });
app.use(staticHosting, { httpPrefix: "/" });
export default app;
This is the fastest serving mode. The component owns /, while your app's
convex/http.ts routes move under /api/....
If existing callbacks or auth routes must stay at the root, use app-owned root routing. To host the site under a sub-path, see Mounting under a sub-path.
3. Add a deploy script
{
"scripts": {
"deploy": "npx @convex-dev/static-hosting deploy"
}
}
That's it.
Keep existing HTTP routes at the root
Do not move stable webhook, auth, or API URLs just to add static hosting. Leave the component's HTTP routes unmounted and register the static catch-all in your existing router.
convex/convex.config.ts:
import { defineApp } from "convex/server";
import staticHosting from "@convex-dev/static-hosting/convex.config";
const app = defineApp();
app.use(staticHosting); // no httpPrefix
export default app;
convex/http.ts:
import { httpRouter } from "convex/server";
import { registerStaticRoutes } from "@convex-dev/static-hosting";
import { components } from "./_generated/api";
const http = httpRouter();
// Register exact app routes first. Existing auth/webhook helpers can stay here.
// auth.addHttpRoutes(http);
registerStaticRoutes(http, components.staticHosting);
export default http;
Exact routes win over the static catch-all, so existing URLs keep working. The component still owns uploads, deployment state, and file storage. This mode adds an internal query and storage fetch on an uncached request, so prefer the component-owned mode when you do not need root-level app routes.
Using non-Vite bundlers
The deploy build step and upload --build set VITE_CONVEX_URL. For bundlers
that use different environment variable conventions, wrap your build script to
pass through the value:
For Expo:
{
"scripts": {
"build": "EXPO_PUBLIC_CONVEX_URL=${VITE_CONVEX_URL:-$EXPO_PUBLIC_CONVEX_URL} npx expo export --platform web"
}
}
For Next.js:
{
"scripts": {
"build": "NEXT_PUBLIC_CONVEX_URL=${VITE_CONVEX_URL:-$NEXT_PUBLIC_CONVEX_URL} next build"
}
}
The pattern ${VITE_CONVEX_URL:-$VAR} uses VITE_CONVEX_URL if set by the CLI
and otherwise falls back to your bundler-specific variable. This keeps both the
CLI-driven build and standalone npm run build working.
Deployment
Deploy both Convex backend and static files with a single command:
npx convex login # first time only
npx @convex-dev/static-hosting deploy
The deploy command:
- Builds your frontend with the production
VITE_CONVEX_URL. - Deploys the Convex backend.
- Uploads
dist/to Convex.
For more control, you can run the two halves separately:
npx convex deploy
npx @convex-dev/static-hosting upload --build --prod
Your app is live at https://<deployment>.convex.site.
Development workflow
Use your normal frontend dev server during development:
# Terminal 1
npx convex dev
# Terminal 2
npm run dev
For Vite, that keeps HMR and fast local feedback. Static hosting is the deploy target, not a replacement dev server. Uploading every edit to a development deployment is slower and loses HMR, even when an agent writes most of the code. Humans still need the quick loop for visual checks, transient UI state, and debugging.
Before release, run one hosted smoke test against the development deployment:
npx @convex-dev/static-hosting upload --build
Then use deploy for production. This split keeps the dev loop fast while still
testing the real HTTP, caching, base-path, and SPA behavior before shipping.
CLI options
npx @convex-dev/static-hosting deploy [options]
-d, --dist <path> Path to dist directory (default: ./dist)
-c, --component <name> Component instance name (default: staticHosting)
--skip-build Skip the build step (use existing dist)
--skip-convex Skip Convex backend deployment
--build-command <cmd> Build command to run (default: 'npm run build')
--no-spa Disable SPA fallback (404 instead of /index.html)
--spa Enable SPA fallback (default)
--cdn Use the legacy convex-fs integration
--cdn-delete-function Legacy app function that deletes CDN blobs
npx @convex-dev/static-hosting upload [options]
-d, --dist <path> Path to dist directory (default: ./dist)
-c, --component <name> Component instance name (default: staticHosting)
--prod Deploy to production deployment
-b, --build Run 'npm run build' with VITE_CONVEX_URL set
--build-command <cmd> Override the build command; implies --build
--no-spa Disable SPA fallback (404 instead of /index.html)
--spa Enable SPA fallback (default)
--cdn Use the legacy convex-fs integration
--cdn-delete-function Legacy app function that deletes CDN blobs
-j, --concurrency <n> Parallel upload workers (default: 5)
Each upload is published atomically, so visitors never see a page that refers to assets that are not available yet. Failed uploads leave the previous deployment live, and old files are cleaned up safely. See INTEGRATION.md for upload limits and lifecycle details.
Convex HTTP routes currently support GET but not HEAD. Configure uptime checks to make a lightweight GET request rather than a HEAD request.
If you mount the component under a different name with
app.use(staticHosting, { name: "custom" }), pass --component custom and
replace every generated components.staticHosting reference with
components.custom.
Do not use --cdn for a new integration. It targets an older ConvexFS HTTP API
and is retained only for existing deployments. Legacy CDN users must keep
app-owned root routing because /fs/upload and /fs/blobs/* are root app
routes. See INTEGRATION.md for the current limitation.
Security
The upload API uses internal functions in the Component that can only be called via:
npx convex run(requires Convex CLI authentication)- Other Convex functions in the Component (server-side only)
This means unauthorized users cannot upload files to your site, even if they know your Convex URL.
Reload prompt after deploy (optional)
If you want a banner that prompts users to reload when a new deployment ships,
expose the deployment query in your app and drop in <UpdateBanner />:
convex/staticHosting.ts:
import { exposeDeploymentQuery } from "@convex-dev/static-hosting";
import { components } from "./_generated/api";
export const { getCurrentDeployment } = exposeDeploymentQuery(
components.staticHosting,
);
src/App.tsx:
import { UpdateBanner } from "@convex-dev/static-hosting/react";
function App() {
return (
<>
<UpdateBanner message="New version!" buttonText="Reload" />
{/* ... */}
</>
);
}
UpdateBanner resolves api.staticHosting.getCurrentDeployment automatically.
If you re-export the query under a different module name, pass it explicitly:
import { api } from "../convex/_generated/api";
<UpdateBanner getCurrentDeployment={api.myModule.getCurrentDeployment} />;
For custom UI, use the hook:
import { useDeploymentUpdates } from "@convex-dev/static-hosting/react";
const { updateAvailable, reload, dismiss } = useDeploymentUpdates();
Mounting under a sub-path
Mount the static site under a sub-path if you have other routes at the root:
app.use(staticHosting, { httpPrefix: "/app/" });
You'll also need to tell your bundler about the base path so the emitted HTML
references the right URLs. The CLI sets a STATIC_HOSTING_BASE_PATH env var
matching the component's mount when it runs your build, so vite.config.ts can
read it directly:
import { defineConfig } from "vite";
export default defineConfig({
base: process.env.STATIC_HOSTING_BASE_PATH ?? "/",
});
Root-mounted apps don't need this; the default is /. For webpack use
publicPath, for Next.js assetPrefix.
SPA routing
By default, requests for a path with no file extension that doesn't match an
uploaded file fall back to index.html, so client-side routes like
/dashboard/settings work on reload. For a multi-page app where unknown paths
should be a real 404, deploy with --no-spa:
npx @convex-dev/static-hosting deploy --no-spa
The setting is stored with the deployment, so it travels with the code you ship
rather than living in a separate env var. Requests for paths with an extension
(e.g. /missing.js) always 404 when not found, regardless of this setting.
Upgrading from 0.1.x
0.2.0 moves uploads and file storage into the component. You must remove the
exposeUploadApi re-exports from convex/staticHosting.ts and redeploy your
assets because 0.1.x files lived in the app's storage. Capture both the
current v1 manifest and a broader app-storage inventory first: older v1 uploads
may have left static blobs that the current manifest no longer lists.
You can then choose either serving mode:
- If the static site can own
/, mount the component there and remove the oldregisterStaticRoutescall. - If existing HTTP routes must stay at
/, keepconvex/http.tsand itsregisterStaticRoutescall. The 0.2 implementation reads the new component-owned files without changing those route URLs.
See the dedicated 0.1.x to 0.2.x migration guide for exact steps, verification, rollback, and the optional staged cutover.
How it works
- Build: your bundler emits
dist/. - Upload: the CLI uses your authenticated Convex session to generate signed upload URLs, push files to Convex storage, record metadata, and GC old deployments.
- Serve: an HTTP action looks up the requested path, streams the file with
the right
Content-Type, applies long-term caching for hashed assets, and falls back toindex.htmlfor SPA routes.
Example
See example/ for a complete Vite + React app.
npm install
npm run dev
Contributing
See CONTRIBUTING.md.
License
Apache-2.0