|
| 1 | +#!/usr/bin/env node |
| 2 | +/** |
| 3 | + * Output integration instructions for LLMs. |
| 4 | + * |
| 5 | + * Usage: |
| 6 | + * npx @get-convex/self-static-hosting init |
| 7 | + */ |
| 8 | + |
| 9 | +const instructions = ` |
| 10 | +# Convex Self Static Hosting - Integration Instructions |
| 11 | +
|
| 12 | +You are integrating the @get-convex/self-static-hosting component into a Convex app. |
| 13 | +This component enables hosting static files (React/Vite apps) directly on Convex. |
| 14 | +
|
| 15 | +## What This Component Does |
| 16 | +
|
| 17 | +- Stores static files in Convex storage |
| 18 | +- Serves files via HTTP actions with proper MIME types |
| 19 | +- Supports SPA routing (fallback to index.html) |
| 20 | +- Smart caching: hashed assets cached forever, HTML revalidates |
| 21 | +- ETag support for efficient cache revalidation |
| 22 | +- Live reload notifications when new deployments happen |
| 23 | +- Optional Cloudflare CDN integration |
| 24 | +
|
| 25 | +## Files to Create/Modify |
| 26 | +
|
| 27 | +### 1. convex/convex.config.ts (create or modify) |
| 28 | +
|
| 29 | +\`\`\`typescript |
| 30 | +import { defineApp } from "convex/server"; |
| 31 | +import selfStaticHosting from "@get-convex/self-static-hosting/convex.config"; |
| 32 | +
|
| 33 | +const app = defineApp(); |
| 34 | +app.use(selfStaticHosting); |
| 35 | +
|
| 36 | +export default app; |
| 37 | +\`\`\` |
| 38 | +
|
| 39 | +### 2. convex/staticHosting.ts (create) |
| 40 | +
|
| 41 | +\`\`\`typescript |
| 42 | +import { components } from "./_generated/api"; |
| 43 | +import { |
| 44 | + exposeUploadApi, |
| 45 | + exposeDeploymentQuery, |
| 46 | + exposeCachePurgeAction, |
| 47 | +} from "@get-convex/self-static-hosting"; |
| 48 | +
|
| 49 | +// Internal functions for secure uploads (only callable via CLI) |
| 50 | +export const { generateUploadUrl, recordAsset, gcOldAssets, listAssets } = |
| 51 | + exposeUploadApi(components.selfStaticHosting); |
| 52 | +
|
| 53 | +// Public query for live reload notifications |
| 54 | +export const { getCurrentDeployment } = |
| 55 | + exposeDeploymentQuery(components.selfStaticHosting); |
| 56 | +
|
| 57 | +// Optional: Cloudflare cache purge (for CI/CD) |
| 58 | +export const { purgeCloudflareCache } = exposeCachePurgeAction(); |
| 59 | +\`\`\` |
| 60 | +
|
| 61 | +### 3. convex/http.ts (create or modify) |
| 62 | +
|
| 63 | +\`\`\`typescript |
| 64 | +import { httpRouter } from "convex/server"; |
| 65 | +import { registerStaticRoutes } from "@get-convex/self-static-hosting"; |
| 66 | +import { components } from "./_generated/api"; |
| 67 | +
|
| 68 | +const http = httpRouter(); |
| 69 | +
|
| 70 | +// Option A: Serve at root (if no other HTTP routes) |
| 71 | +registerStaticRoutes(http, components.selfStaticHosting); |
| 72 | +
|
| 73 | +// Option B: Serve at /app/ prefix (recommended if you have API routes) |
| 74 | +// registerStaticRoutes(http, components.selfStaticHosting, { |
| 75 | +// pathPrefix: "/app", |
| 76 | +// }); |
| 77 | +
|
| 78 | +// Add other HTTP routes here if needed |
| 79 | +// http.route({ path: "/api/webhook", method: "POST", handler: ... }); |
| 80 | +
|
| 81 | +export default http; |
| 82 | +\`\`\` |
| 83 | +
|
| 84 | +### 4. package.json scripts (add) |
| 85 | +
|
| 86 | +\`\`\`json |
| 87 | +{ |
| 88 | + "scripts": { |
| 89 | + "build": "vite build", |
| 90 | + "deploy:static": "npm run build && npx @get-convex/self-static-hosting upload" |
| 91 | + } |
| 92 | +} |
| 93 | +\`\`\` |
| 94 | +
|
| 95 | +If using a path prefix, specify the module: |
| 96 | +\`\`\`json |
| 97 | +{ |
| 98 | + "scripts": { |
| 99 | + "deploy:static": "npm run build && npx @get-convex/self-static-hosting upload --module staticHosting" |
| 100 | + } |
| 101 | +} |
| 102 | +\`\`\` |
| 103 | +
|
| 104 | +### 5. src/main.tsx (modify entry point) |
| 105 | +
|
| 106 | +\`\`\`typescript |
| 107 | +import React from "react"; |
| 108 | +import ReactDOM from "react-dom/client"; |
| 109 | +import { ConvexProvider, ConvexReactClient } from "convex/react"; |
| 110 | +import { getConvexUrlWithFallback } from "@get-convex/self-static-hosting"; |
| 111 | +import App from "./App"; |
| 112 | +
|
| 113 | +// Auto-detects Convex URL when deployed to *.convex.site |
| 114 | +const convexUrl = getConvexUrlWithFallback(import.meta.env.VITE_CONVEX_URL); |
| 115 | +const convex = new ConvexReactClient(convexUrl); |
| 116 | +
|
| 117 | +ReactDOM.createRoot(document.getElementById("root")!).render( |
| 118 | + <React.StrictMode> |
| 119 | + <ConvexProvider client={convex}> |
| 120 | + <App /> |
| 121 | + </ConvexProvider> |
| 122 | + </React.StrictMode> |
| 123 | +); |
| 124 | +\`\`\` |
| 125 | +
|
| 126 | +### 6. src/App.tsx (optional: add live reload banner) |
| 127 | +
|
| 128 | +\`\`\`typescript |
| 129 | +import { UpdateBanner } from "@get-convex/self-static-hosting/react"; |
| 130 | +import { api } from "../convex/_generated/api"; |
| 131 | +
|
| 132 | +function App() { |
| 133 | + return ( |
| 134 | + <div> |
| 135 | + {/* Shows banner when new deployment is available */} |
| 136 | + <UpdateBanner |
| 137 | + getCurrentDeployment={api.staticHosting.getCurrentDeployment} |
| 138 | + message="New version available!" |
| 139 | + buttonText="Refresh" |
| 140 | + /> |
| 141 | + |
| 142 | + {/* Rest of your app */} |
| 143 | + </div> |
| 144 | + ); |
| 145 | +} |
| 146 | +\`\`\` |
| 147 | +
|
| 148 | +Or use the hook for custom UI: |
| 149 | +\`\`\`typescript |
| 150 | +import { useDeploymentUpdates } from "@get-convex/self-static-hosting/react"; |
| 151 | +import { api } from "../convex/_generated/api"; |
| 152 | +
|
| 153 | +function App() { |
| 154 | + const { updateAvailable, reload, dismiss } = useDeploymentUpdates( |
| 155 | + api.staticHosting.getCurrentDeployment |
| 156 | + ); |
| 157 | + |
| 158 | + // Custom update notification UI |
| 159 | +} |
| 160 | +\`\`\` |
| 161 | +
|
| 162 | +## Deployment |
| 163 | +
|
| 164 | +\`\`\`bash |
| 165 | +# Login to Convex (first time) |
| 166 | +npx convex login |
| 167 | +
|
| 168 | +# Push Convex functions |
| 169 | +npx convex dev --once |
| 170 | +
|
| 171 | +# Build and deploy static files |
| 172 | +npm run deploy:static |
| 173 | +
|
| 174 | +# Your app is now live at: |
| 175 | +# https://your-deployment.convex.site |
| 176 | +# (or https://your-deployment.convex.site/app/ if using path prefix) |
| 177 | +\`\`\` |
| 178 | +
|
| 179 | +## Optional: Cloudflare CDN |
| 180 | +
|
| 181 | +For production with custom domain and edge caching: |
| 182 | +
|
| 183 | +\`\`\`bash |
| 184 | +# Login to Cloudflare |
| 185 | +npx wrangler login |
| 186 | +
|
| 187 | +# Deploy with automatic cache purge |
| 188 | +npx @get-convex/self-static-hosting upload --domain yourdomain.com |
| 189 | +\`\`\` |
| 190 | +
|
| 191 | +Or for CI/CD, set environment variables: |
| 192 | +\`\`\`bash |
| 193 | +export CLOUDFLARE_ZONE_ID="your-zone-id" |
| 194 | +export CLOUDFLARE_API_TOKEN="your-api-token" |
| 195 | +npm run deploy:static |
| 196 | +\`\`\` |
| 197 | +
|
| 198 | +## Important Notes |
| 199 | +
|
| 200 | +1. The upload functions are INTERNAL - they can only be called via \`npx convex run\`, not from the public internet |
| 201 | +2. Static files are stored in the app's storage (not the component's) for proper isolation |
| 202 | +3. Hashed assets (e.g., main-abc123.js) get immutable caching; HTML files always revalidate |
| 203 | +4. The component supports SPA routing - routes without file extensions serve index.html |
| 204 | +`; |
| 205 | + |
| 206 | +console.log(instructions); |
0 commit comments