@convex-dev/self-hosting
v0.1.1
Published
A self hosting component for Convex.
Readme
Convex Self Hosting
A Convex component that enables self-hosting static React/Vite apps using Convex HTTP actions and file storage. No external hosting provider required!
Quick Start
Automated Setup (Recommended)
npm install @convex-dev/self-hosting
npx @convex-dev/self-hosting setupThe interactive wizard will:
- Create necessary Convex files
- Add deploy script to package.json
Then deploy:
npm run deployFor LLMs / AI Assistants
If you're an LLM helping a user integrate this component, read INTEGRATION.md for complete integration instructions optimized for AI consumption.
Manual Setup
See Manual Setup section below for step-by-step instructions.
Features
- 🚀 Simple deployment - Upload your built files directly to Convex storage
- 🔒 Secure by default - Upload API uses internal functions (not publicly accessible)
- 🔄 SPA support - Automatic fallback to index.html for client-side routing
- ⚡ Smart caching - Hashed assets get long-term caching, HTML is always fresh with ETag support
- 🧹 Auto cleanup - Old deployment files are automatically garbage collected
- 📦 Zero config - Works out of the box with Vite, Create React App, and other bundlers
https://github.com/user-attachments/assets/5eaf781f-87da-4292-9f96-38070c86cd39
Manual Setup
1. Install
npm install @convex-dev/self-hosting2. Add to your convex/convex.config.ts:
import { defineApp } from "convex/server";
import selfHosting from "@convex-dev/self-hosting/convex.config.js";
const app = defineApp();
app.use(selfHosting);
export default app;3. Register HTTP routes
Create or update convex/http.ts to serve static files:
import { httpRouter } from "convex/server";
import { registerStaticRoutes } from "@convex-dev/self-hosting";
import { components } from "./_generated/api";
const http = httpRouter();
// Serve static files at the root path with SPA fallback
registerStaticRoutes(http, components.selfHosting);
export default http;4. Expose upload API (internal functions)
Create a file like convex/staticHosting.ts:
import { exposeUploadApi } from "@convex-dev/self-hosting";
import { components } from "./_generated/api";
// These are INTERNAL functions - only callable via `npx convex run`
// NOT accessible from the public internet
export const { generateUploadUrl, recordAsset, gcOldAssets, listAssets } =
exposeUploadApi(components.selfHosting);Note: Run npx convex dev at least once after setup to push your schema and
enable HTTP actions. If you see the error "This Convex deployment does not have
HTTP actions enabled", it means the Convex backend hasn't been deployed yet.
5. Add deploy script to package.json
{
"scripts": {
"build": "vite build",
"deploy:static": "npx @convex-dev/self-hosting upload --build --prod"
}
}Important: Use --build to ensure VITE_CONVEX_URL is set correctly for
production. Don't run npm run build separately before the upload command, as
that would use the dev URL from .env.local.
CLI Options:
npx @convex-dev/self-hosting upload [options]
Options:
-d, --dist <path> Path to dist directory (default: ./dist)
-c, --component <name> Convex component name (default: staticHosting)
--prod Deploy to production Convex deployment
--dev Deploy to dev deployment (default)
-b, --build Run 'npm run build' with correct VITE_CONVEX_URL
-h, --help Show helpExamples:
# Deploy to production with automatic build
npx @convex-dev/self-hosting upload --build --prod
# Deploy to dev (for testing)
npx @convex-dev/self-hosting upload --buildUsing Non-Vite Bundlers
The CLI's --build flag sets VITE_CONVEX_URL when running your build command.
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),
otherwise falls back to your bundler-specific variable. This allows the CLI's
--build flag to work correctly while keeping your standalone npm run build
functional.
Deployment
One-Shot Deployment (Recommended)
Deploy both Convex backend and static files with a single command:
# Make sure you're logged in
npx convex login
# Deploy everything
npx @convex-dev/self-hosting deployThe deploy command:
- Builds frontend with production
VITE_CONVEX_URL - Deploys Convex backend (
npx convex deploy) - Deploys static files to Convex storage
This minimizes the inconsistency window between backend and frontend updates.
Deploy command options:
npx @convex-dev/self-hosting deploy [options]
Options:
-d, --dist <path> Path to dist directory (default: ./dist)
-c, --component <name> Convex component name (default: staticHosting)
--skip-build Skip the build step (use existing dist)
--skip-convex Skip Convex backend deployment
-h, --help Show helpAdd to package.json for easy deployments:
{
"scripts": {
"deploy": "npx @convex-dev/self-hosting deploy"
}
}Manual Two-Step Deployment
If you prefer more control, deploy separately:
# Deploy Convex backend
npx convex deploy
# Deploy static files
npx @convex-dev/self-hosting upload --build --prodYour app is now live at https://your-deployment.convex.site
Security
The upload API uses internal functions that can only be called via:
npx convex run(requires Convex CLI authentication)- Other Convex functions (server-side only)
This means unauthorized users cannot upload files to your site, even if they know your Convex URL.
Live Reload on Deploy
Connected clients can be notified when a new deployment is available:
Expose the deployment query:
import { exposeDeploymentQuery } from "@convex-dev/self-hosting"; import { components } from "./_generated/api"; export const { getCurrentDeployment } = exposeDeploymentQuery( components.selfHosting, );Add the update banner to your app:
import { UpdateBanner } from "@convex-dev/self-hosting/react"; import { api } from "../convex/_generated/api"; function App() { return ( <div> <UpdateBanner getCurrentDeployment={api.staticHosting.getCurrentDeployment} message="New version available!" buttonText="Refresh" /> {/* rest of your app */} </div> ); }
Or use the hook for custom UI:
import { useDeploymentUpdates } from "@convex-dev/self-hosting/react";
const { updateAvailable, reload, dismiss } = useDeploymentUpdates(
api.staticHosting.getCurrentDeployment,
);Configuration Options
registerStaticRoutes
registerStaticRoutes(http, components.selfHosting, {
// URL prefix for static files (default: "/")
pathPrefix: "/app",
// Enable SPA fallback to index.html (default: true)
spaFallback: true,
});How It Works
- Build Phase: Your bundler (Vite, etc.) creates optimized files in
dist/ - Upload Phase: The upload script uses
npx convex runto:- Generate signed upload URLs
- Upload each file to Convex storage
- Record file metadata in the component's database
- Garbage collect files from previous deployments
- Serve Phase: HTTP actions serve files from storage with:
- Correct Content-Type headers
- Smart cache control (immutable for hashed assets)
- SPA fallback for client-side routing
Example
Check out the example directory for a complete working example.
npm install
npm run devContributing
See CONTRIBUTING.md for development setup and guidelines.
License
Apache-2.0
