@samebase/convex-hosting 0.0.0-stage → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/CHANGELOG.md +133 -0
  2. package/INTEGRATION.md +399 -0
  3. package/LICENSE +201 -0
  4. package/NOTICE +7 -0
  5. package/README.md +471 -2
  6. package/dist/cli/args.d.ts +28 -0
  7. package/dist/cli/args.d.ts.map +1 -0
  8. package/dist/cli/args.js +148 -0
  9. package/dist/cli/args.js.map +1 -0
  10. package/dist/cli/argumentChunks.d.ts +3 -0
  11. package/dist/cli/argumentChunks.d.ts.map +1 -0
  12. package/dist/cli/argumentChunks.js +29 -0
  13. package/dist/cli/argumentChunks.js.map +1 -0
  14. package/dist/cli/collectFiles.d.ts +10 -0
  15. package/dist/cli/collectFiles.d.ts.map +1 -0
  16. package/dist/cli/collectFiles.js +53 -0
  17. package/dist/cli/collectFiles.js.map +1 -0
  18. package/dist/cli/commands.d.ts +8 -0
  19. package/dist/cli/commands.d.ts.map +1 -0
  20. package/dist/cli/commands.js +56 -0
  21. package/dist/cli/commands.js.map +1 -0
  22. package/dist/cli/componentName.d.ts +32 -0
  23. package/dist/cli/componentName.d.ts.map +1 -0
  24. package/dist/cli/componentName.js +43 -0
  25. package/dist/cli/componentName.js.map +1 -0
  26. package/dist/cli/deploy.d.ts +16 -0
  27. package/dist/cli/deploy.d.ts.map +1 -0
  28. package/dist/cli/deploy.js +283 -0
  29. package/dist/cli/deploy.js.map +1 -0
  30. package/dist/cli/deployEnvironment.d.ts +10 -0
  31. package/dist/cli/deployEnvironment.d.ts.map +1 -0
  32. package/dist/cli/deployEnvironment.js +12 -0
  33. package/dist/cli/deployEnvironment.js.map +1 -0
  34. package/dist/cli/index.d.ts +12 -0
  35. package/dist/cli/index.d.ts.map +1 -0
  36. package/dist/cli/index.js +60 -0
  37. package/dist/cli/index.js.map +1 -0
  38. package/dist/cli/upload.d.ts +22 -0
  39. package/dist/cli/upload.d.ts.map +1 -0
  40. package/dist/cli/upload.js +630 -0
  41. package/dist/cli/upload.js.map +1 -0
  42. package/dist/client/_generated/_ignore.d.ts +1 -0
  43. package/dist/client/_generated/_ignore.d.ts.map +1 -0
  44. package/dist/client/_generated/_ignore.js +3 -0
  45. package/dist/client/_generated/_ignore.js.map +1 -0
  46. package/dist/client/index.d.ts +119 -0
  47. package/dist/client/index.d.ts.map +1 -0
  48. package/dist/client/index.js +272 -0
  49. package/dist/client/index.js.map +1 -0
  50. package/dist/component/_generated/api.d.ts +38 -0
  51. package/dist/component/_generated/api.d.ts.map +1 -0
  52. package/dist/component/_generated/api.js +31 -0
  53. package/dist/component/_generated/api.js.map +1 -0
  54. package/dist/component/_generated/component.d.ts +43 -0
  55. package/dist/component/_generated/component.d.ts.map +1 -0
  56. package/dist/component/_generated/component.js +11 -0
  57. package/dist/component/_generated/component.js.map +1 -0
  58. package/dist/component/_generated/dataModel.d.ts +46 -0
  59. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  60. package/dist/component/_generated/dataModel.js +11 -0
  61. package/dist/component/_generated/dataModel.js.map +1 -0
  62. package/dist/component/_generated/server.d.ts +121 -0
  63. package/dist/component/_generated/server.d.ts.map +1 -0
  64. package/dist/component/_generated/server.js +78 -0
  65. package/dist/component/_generated/server.js.map +1 -0
  66. package/dist/component/convex.config.d.ts +3 -0
  67. package/dist/component/convex.config.d.ts.map +1 -0
  68. package/dist/component/convex.config.js +3 -0
  69. package/dist/component/convex.config.js.map +1 -0
  70. package/dist/component/http.d.ts +4 -0
  71. package/dist/component/http.d.ts.map +1 -0
  72. package/dist/component/http.js +125 -0
  73. package/dist/component/http.js.map +1 -0
  74. package/dist/component/lib.d.ts +164 -0
  75. package/dist/component/lib.d.ts.map +1 -0
  76. package/dist/component/lib.js +768 -0
  77. package/dist/component/lib.js.map +1 -0
  78. package/dist/component/schema.d.ts +84 -0
  79. package/dist/component/schema.d.ts.map +1 -0
  80. package/dist/component/schema.js +69 -0
  81. package/dist/component/schema.js.map +1 -0
  82. package/dist/component/serving.d.ts +17 -0
  83. package/dist/component/serving.d.ts.map +1 -0
  84. package/dist/component/serving.js +150 -0
  85. package/dist/component/serving.js.map +1 -0
  86. package/dist/react/index.d.ts +59 -0
  87. package/dist/react/index.d.ts.map +1 -0
  88. package/dist/react/index.js +137 -0
  89. package/dist/react/index.js.map +1 -0
  90. package/package.json +78 -3
  91. package/src/cli/args.test.ts +79 -0
  92. package/src/cli/args.ts +153 -0
  93. package/src/cli/argumentChunks.test.ts +27 -0
  94. package/src/cli/argumentChunks.ts +40 -0
  95. package/src/cli/collectFiles.test.ts +26 -0
  96. package/src/cli/collectFiles.ts +59 -0
  97. package/src/cli/commands.ts +68 -0
  98. package/src/cli/componentName.test.ts +41 -0
  99. package/src/cli/componentName.ts +46 -0
  100. package/src/cli/deploy.ts +336 -0
  101. package/src/cli/deployEnvironment.test.ts +27 -0
  102. package/src/cli/deployEnvironment.ts +17 -0
  103. package/src/cli/index.ts +65 -0
  104. package/src/cli/upload.ts +837 -0
  105. package/src/client/_generated/_ignore.ts +1 -0
  106. package/src/client/index.test.ts +510 -0
  107. package/src/client/index.ts +355 -0
  108. package/src/client/setup.test.ts +22 -0
  109. package/src/component/_generated/api.ts +54 -0
  110. package/src/component/_generated/component.ts +55 -0
  111. package/src/component/_generated/dataModel.ts +60 -0
  112. package/src/component/_generated/server.ts +156 -0
  113. package/src/component/convex.config.ts +3 -0
  114. package/src/component/http.test.ts +223 -0
  115. package/src/component/http.ts +144 -0
  116. package/src/component/lib.test.ts +1007 -0
  117. package/src/component/lib.ts +862 -0
  118. package/src/component/schema.ts +75 -0
  119. package/src/component/serving.ts +158 -0
  120. package/src/component/setup.test.ts +11 -0
  121. package/src/react/index.tsx +197 -0
  122. package/src/test.ts +23 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,133 @@
1
+ # Changelog
2
+
3
+ ## 0.3.1
4
+
5
+ - `upload` and `deploy` no longer upload `_headers` and `_redirects` from the root of `dist/`.
6
+ Cloudflare and Netlify read these files as hosting rules. Before this change the site served them
7
+ as public files.
8
+
9
+ ## 0.3.0
10
+
11
+ First release as `@samebase/convex-hosting`, Samebase's fork of `@convex-dev/static-hosting`. See
12
+ "Switching from @convex-dev/static-hosting" in the README.
13
+
14
+ - The package, the CLI command (`convex-hosting`), the component (`hosting`), the default deployment
15
+ query module (`convex/hosting.ts`), and the `CONVEX_HOSTING_BASE_PATH` build variable share one
16
+ name. The CLI still finds an instance named `staticHosting`.
17
+ - `serveStaticAsset` serves one uploaded file from your own HTTP route, for custom routing and
18
+ server rendering.
19
+ - `upload --preview-name` uploads to a named preview deployment.
20
+ - Replaced files stay available for at least seven days, so pages that are already open can still
21
+ load their scripts.
22
+ - The `setup` and `init` commands are removed. The README describes the setup steps.
23
+
24
+ Versions up to 0.2.1 were released as `@convex-dev/static-hosting`.
25
+
26
+ ## 0.2.1
27
+
28
+ - Smoother migration story, with more automatic fallbacks to the old component name.
29
+
30
+ ## 0.2.0
31
+
32
+ Component-owned HTTP and storage.
33
+
34
+ **Breaking — redeploy your static assets after upgrading.**
35
+
36
+ See the [0.1.x to 0.2.x migration guide](./MIGRATION.md) before upgrading an existing deployment.
37
+
38
+ - The component now hosts its own HTTP endpoints and owns the file storage that serves them. Wire it
39
+ up with `app.use(staticHosting, { httpPrefix: "/" })` and delete related code in`convex/http.ts` +
40
+ the upload-API re-exports from `convex/staticHosting.ts`.
41
+ - Removed `exposeUploadApi` from the client API. Uploads and file lifecycle are now handled by
42
+ private component functions.
43
+ - `registerStaticRoutes` remains as a compatibility mode for apps that need to keep existing auth,
44
+ webhook, or API routes at the root. It serves the new component-owned files from the app's
45
+ existing `convex/http.ts` catch-all. During a same-name v1→v2 migration it also serves inherited
46
+ v1 files straight from the app's own storage, so the live site stays up with no re-upload gap.
47
+ Mounting the component's own handler is still the faster default.
48
+ - `exposeDeploymentQuery` and `getConvexUrl` remain if you use the UpdateBanner.
49
+ - The component is now named `staticHosting` (previously `selfHosting`). The CLI invokes it directly
50
+ via `npx convex run --component staticHosting lib:...`. If you mount the component under a
51
+ different name, pass `--component <your-name>`. For a smooth upgrade, pass {name: "selfHosting} to
52
+ `app.use(staticHosting, { name: "selfHosting" })`
53
+ - `useDeploymentUpdates` / `UpdateBanner` use `useQuery_experimental` and default to
54
+ `api.staticHosting.getCurrentDeployment`. If you don't surface deployment updates, you no longer
55
+ need to expose anything.
56
+ - Assets uploaded under 0.1.x lived in the app's storage — the component-owned HTTP handler can't
57
+ resolve those references in 0.2.x. Run `npx @convex-dev/static-hosting deploy` to repopulate. (In
58
+ app-owned root routing the app-side handler keeps serving those v1 files from app storage until
59
+ the first upload replaces them, so that mode has no asset-less window.) The first 0.2.x upload
60
+ safely discards those legacy storage references instead of trying to delete them from component
61
+ storage. The old blobs remain in app storage until you explicitly clean them up after the rollback
62
+ window. Older 0.1.x deploys may also have left historical blobs that the current asset manifest no
63
+ longer lists, so follow the migration guide's app-storage audit before cleanup.
64
+ - Default setup prefixes your own HTTP routes with `defineApp({ httpPrefix: "/api" })` so the static
65
+ site can own the root without the catch-all route shadowing them. Use `registerStaticRoutes` when
66
+ those existing root route URLs cannot move.
67
+ - SPA fallback is now configurable per deployment. Deploy with `--no-spa` (`deploy` or `upload`) to
68
+ make extension-less misses return 404 instead of `index.html`. The setting is stored on the
69
+ deployment record, so it travels with the code you ship.
70
+ - Hashed-asset caching recognizes Vite's base64url hashes (including `-`) and numeric extensions
71
+ such as `.woff2`. HTML always uses revalidation, even if its filename looks content-hashed.
72
+ - The `cdnBaseUrl` override remains available in `registerStaticRoutes` compatibility mode.
73
+ Component-owned HTTP routes redirect CDN blobs to the deployment's own `{origin}/fs/blobs`.
74
+ - `--cdn` is now documented as a legacy option. Its setup guide targeted an old unauthenticated
75
+ convex-fs API and does not work with current ConvexFS. New integrations should use Convex storage
76
+ until authenticated CDN uploads are designed.
77
+ - Storage and legacy CDN assets, deployment settings, and old-file cleanup are published in one
78
+ atomic mutation after all uploads finish. New HTML cannot race ahead of its referenced asset
79
+ records, and a failed publish leaves the previous deployment live. Replaced CDN blob IDs are
80
+ retained for cleanup.
81
+ - Upload errors now report the failing file and HTTP status. Oversized manifests fail with a clear
82
+ size error, and newly uploaded component files are removed after a failed upload or publish.
83
+ - Static handlers decode percent-encoded paths, accept weak or listed ETags, and return a
84
+ non-cacheable HTTP 503 setup page until the first upload succeeds.
85
+ - Removed the install-time console banner.
86
+
87
+ ## 0.1.5
88
+
89
+ - Forward-compatibility for 0.2.x rollbacks. The `deploymentInfo` schema and the
90
+ `getCurrentDeployment` return validator now allow the optional `spaFallback` (boolean) and
91
+ `pendingBlobCleanupCount` (number) fields that 0.2.x writes to the deployment record. 0.1.5
92
+ ignores them, but allowing them means a deployment upgraded to 0.2.x and later rolled back to
93
+ 0.1.x keeps validating. No behavior change for 0.1.x users; upgrade to 0.1.5 before trying 0.2.x
94
+ if you want a clean rollback path. (0.2.x also creates additional tables such as `stagedAssets`
95
+ and `cleanupState`; extra tables don't fail 0.1.x schema validation, so they need no entry here.)
96
+
97
+ ## 0.1.4
98
+
99
+ - Added support for Windows
100
+
101
+ ## 0.1.3
102
+
103
+ - Fix missing README in published package
104
+
105
+ ## 0.1.2
106
+
107
+ - Rename package from `@convex-dev/self-hosting` to `@convex-dev/static-hosting`
108
+
109
+ ## 0.1.1-alpha.0
110
+
111
+ - Add optional convex-fs CDN mode for static asset serving (`--cdn` flag)
112
+ - Non-HTML assets can be served from CDN edge network via convex-fs
113
+ - New `cdnBaseUrl` option on `registerStaticRoutes`
114
+ - Schema: `storageId` now optional, new `blobId` field for CDN assets
115
+ - `gcOldAssets` returns `{ deleted, blobIds }` (breaking change from number return)
116
+
117
+ ## 0.1.2-alpha.1
118
+
119
+ removed cloudflare for now.
120
+
121
+ ## 0.1.1
122
+
123
+ - Setup wizard now configures custom domains for Cloudflare Workers
124
+ - Added note about running `convex dev` for HTTP actions error
125
+ - Added documentation for non-Vite bundlers (Expo, Next.js)
126
+
127
+ ## 0.1.0
128
+
129
+ - Migrated from Cloudflare Pages to Workers Static Assets
130
+
131
+ ## 0.0.0
132
+
133
+ - Initial release.
package/INTEGRATION.md ADDED
@@ -0,0 +1,399 @@
1
+ # Integration Guide: @samebase/convex-hosting
2
+
3
+ This guide walks you through hosting a static React/Vite app on Convex using the
4
+ `@samebase/convex-hosting` component. Your frontend ends up at `https://<deployment>.convex.site`,
5
+ served alongside your Convex backend with SPA routing and smart caching.
6
+
7
+ ## What this component gives you
8
+
9
+ - A drop-in HTTP handler that serves your static files from Convex storage
10
+ - One CLI command (`deploy`) that builds, deploys the backend, and uploads files
11
+ - SPA fallback to `index.html` for client-side routing
12
+ - Long-term cache headers on hashed assets and ETag-based revalidation on HTML
13
+ - Optional live-reload notifications when a new deploy ships
14
+ - Authenticated uploads via the Convex CLI (no public upload endpoint)
15
+
16
+ ## Quick Start
17
+
18
+ ```bash
19
+ npm install @samebase/convex-hosting
20
+ ```
21
+
22
+ Register the component and add a `deploy` script as described in [Manual Setup](#manual-setup).
23
+ Then:
24
+
25
+ ```bash
26
+ npm run deploy
27
+ ```
28
+
29
+ ## Manual Setup
30
+
31
+ ### `convex/convex.config.ts`
32
+
33
+ The default setup mounts the component's HTTP handler at the root and moves app-owned routes under
34
+ `/api`.
35
+
36
+ ```typescript
37
+ import { defineApp } from "convex/server";
38
+ import hosting from "@samebase/convex-hosting/convex.config";
39
+
40
+ // Serve your own HTTP endpoints (convex/http.ts) under /api so the static
41
+ // site can own the root.
42
+ const app = defineApp({ httpPrefix: "/api" });
43
+ app.use(hosting, { httpPrefix: "/" });
44
+
45
+ export default app;
46
+ ```
47
+
48
+ This is the fastest serving mode because the component reads its storage directly. It is appropriate
49
+ for new apps and apps whose HTTP routes can live under `/api`.
50
+
51
+ If existing webhook, auth, or API routes must stay at the root, use
52
+ [App-owned root routing](#app-owned-root-routing). To host the static site under a sub-path, see
53
+ [Mounting under a sub-path](#mounting-under-a-sub-path).
54
+
55
+ > Run `npx convex dev` after editing `convex.config.ts` so codegen picks up the component.
56
+
57
+ ### App-owned root routing
58
+
59
+ Keep the app in control of `convex/http.ts` when changing existing route URLs would break clients or
60
+ third-party callbacks.
61
+
62
+ `convex/convex.config.ts`:
63
+
64
+ ```typescript
65
+ import { defineApp } from "convex/server";
66
+ import hosting from "@samebase/convex-hosting/convex.config";
67
+
68
+ const app = defineApp();
69
+ app.use(hosting); // no httpPrefix
70
+
71
+ export default app;
72
+ ```
73
+
74
+ `convex/http.ts`:
75
+
76
+ ```typescript
77
+ import { httpRouter } from "convex/server";
78
+ import { registerStaticRoutes } from "@samebase/convex-hosting";
79
+ import { components } from "./_generated/api";
80
+
81
+ const http = httpRouter();
82
+
83
+ // Keep existing exact routes at their current URLs.
84
+ // auth.addHttpRoutes(http);
85
+ registerStaticRoutes(http, components.hosting);
86
+
87
+ export default http;
88
+ ```
89
+
90
+ Exact routes win over the static catch-all. Uploads, deployment metadata, SPA configuration, and
91
+ file storage remain inside the component, so you can remove the old `exposeUploadApi` wrappers.
92
+ During a same-name 0.1.x→0.2.x migration this mode also keeps serving your existing v1 files from
93
+ app storage until the first 0.2.x upload, so the live site never drops to the setup page. This
94
+ compatibility mode adds an internal query and storage fetch on uncached requests. Use the
95
+ component-owned handler when preserving root-level app routes is not necessary.
96
+
97
+ ### `package.json`
98
+
99
+ ```json
100
+ {
101
+ "scripts": {
102
+ "deploy": "npx @samebase/convex-hosting deploy"
103
+ }
104
+ }
105
+ ```
106
+
107
+ ## Deployment
108
+
109
+ ```bash
110
+ # First time
111
+ npx convex login
112
+
113
+ # Build + backend deploy + upload static files
114
+ npx @samebase/convex-hosting deploy
115
+ ```
116
+
117
+ Two-step alternative:
118
+
119
+ ```bash
120
+ npx convex deploy
121
+ npx @samebase/convex-hosting upload --build --prod
122
+ ```
123
+
124
+ Your app is live at `https://<deployment>.convex.site`.
125
+
126
+ ## Development workflow
127
+
128
+ Do not use uploaded static files as the main development loop. Run the normal frontend dev server
129
+ alongside Convex:
130
+
131
+ ```bash
132
+ npx convex dev
133
+ npm run dev
134
+ ```
135
+
136
+ Vite keeps HMR, source maps, and transient UI state intact. Agent-written code does not change that
137
+ tradeoff. HMR may matter less while code is being produced, but it still matters when a person
138
+ verifies visuals and debugs interactions.
139
+
140
+ Use the hosted development deployment as a smoke-test target before release:
141
+
142
+ ```bash
143
+ npx @samebase/convex-hosting upload --build
144
+ ```
145
+
146
+ That catches differences in HTTP routing, cache headers, SPA fallback, and base paths. Production
147
+ remains the one-command `deploy` flow.
148
+
149
+ ## Live reload banner (optional)
150
+
151
+ Show users a banner when a new version is deployed:
152
+
153
+ ### `convex/hosting.ts`
154
+
155
+ ```typescript
156
+ import { exposeDeploymentQuery } from "@samebase/convex-hosting";
157
+ import { components } from "./_generated/api";
158
+
159
+ export const { getCurrentDeployment } = exposeDeploymentQuery(components.hosting);
160
+ ```
161
+
162
+ ### Frontend
163
+
164
+ ```tsx
165
+ import { UpdateBanner } from "@samebase/convex-hosting/react";
166
+
167
+ function App() {
168
+ return (
169
+ <>
170
+ <UpdateBanner message="New version available!" buttonText="Reload" />
171
+ {/* rest of app */}
172
+ </>
173
+ );
174
+ }
175
+ ```
176
+
177
+ `UpdateBanner` resolves `api.hosting.getCurrentDeployment` by default. To re-export the query under
178
+ a different module, pass the reference explicitly:
179
+
180
+ ```tsx
181
+ import { api } from "../convex/_generated/api";
182
+ <UpdateBanner getCurrentDeployment={api.myModule.getCurrentDeployment} />;
183
+ ```
184
+
185
+ For custom UI, use the hook:
186
+
187
+ ```tsx
188
+ import { useDeploymentUpdates } from "@samebase/convex-hosting/react";
189
+
190
+ const { updateAvailable, reload, dismiss } = useDeploymentUpdates();
191
+ ```
192
+
193
+ ## Legacy CDN mode
194
+
195
+ Do not use `--cdn` for a new integration. It targets an older, unauthenticated `convex-fs` HTTP API.
196
+ Current ConvexFS releases require app-owned routes and upload authentication, which this CLI does
197
+ not yet provide.
198
+
199
+ Existing deployments that already expose the legacy routes can keep using the flag during migration,
200
+ but they must use app-owned root compatibility mode. `/fs/upload` and `/fs/blobs/*` are app routes,
201
+ so component-owned root mode would break both upload and serving. Everyone else should use the
202
+ default Convex storage mode. Supporting current ConvexFS properly needs a separate authenticated
203
+ upload and garbage-collection design, not a copy-paste configuration snippet.
204
+
205
+ The CLI uploads every file before atomically publishing the mixed storage/CDN manifest and
206
+ deployment settings while queuing exact old-file IDs for bounded follow-up cleanup. New HTML does
207
+ not point at asset records that are still missing, and a failed publish leaves the previous
208
+ deployment live. Failure cleanup removes new files only after it can prove publication did not win
209
+ the transaction race; otherwise later maintenance recovers them. Failed legacy CDN uploads can only
210
+ be cleaned automatically when `--cdn-delete-function` is configured. Old CDN IDs are kept in a
211
+ private cleanup queue until that function succeeds, so a lost publish response does not make them
212
+ unrecoverable.
213
+
214
+ ## Non-Vite bundlers
215
+
216
+ The `deploy` build step and `upload --build` set `VITE_CONVEX_URL`. To forward it to another env var
217
+ (Expo, Next.js, etc.), wrap your build script:
218
+
219
+ ```text
220
+ Expo build script:
221
+ EXPO_PUBLIC_CONVEX_URL=${VITE_CONVEX_URL:-$EXPO_PUBLIC_CONVEX_URL} npx expo export --platform web
222
+
223
+ Next.js build script:
224
+ NEXT_PUBLIC_CONVEX_URL=${VITE_CONVEX_URL:-$NEXT_PUBLIC_CONVEX_URL} next build
225
+ ```
226
+
227
+ ## Security
228
+
229
+ Upload functions are **internal** to the component. They can only be called via:
230
+
231
+ - `npx convex run` (requires Convex CLI authentication)
232
+ - Other Convex functions (server-side only)
233
+
234
+ This means unauthorized users cannot upload files, even if they know your Convex URL.
235
+
236
+ ## CLI Reference
237
+
238
+ ```bash
239
+ npx @samebase/convex-hosting deploy [options]
240
+ -d, --dist <path> Path to dist directory (default: ./dist)
241
+ -c, --component <name> Component instance name (default: hosting)
242
+ --skip-build Skip the build step
243
+ --skip-convex Skip Convex backend deployment
244
+ --build-command <cmd> Build command to run (default: 'npm run build')
245
+ --no-spa Disable SPA fallback (404 instead of /index.html)
246
+ --spa Enable SPA fallback (default)
247
+ --cdn Upload non-HTML assets to convex-fs CDN
248
+ --cdn-delete-function App function path that deletes CDN blobs
249
+
250
+ npx @samebase/convex-hosting upload [options]
251
+ -d, --dist <path> Path to dist directory (default: ./dist)
252
+ -c, --component <name> Component instance name (default: hosting)
253
+ --prod Deploy to production deployment
254
+ --preview-name <name> Upload to the named preview deployment
255
+ -b, --build Run 'npm run build' with VITE_CONVEX_URL set
256
+ --build-command <cmd> Override the build command; implies --build
257
+ --no-spa Disable SPA fallback (404 instead of /index.html)
258
+ --spa Enable SPA fallback (default)
259
+ --cdn Upload non-HTML assets to convex-fs CDN
260
+ --cdn-delete-function App function path that deletes CDN blobs
261
+ -j, --concurrency <n> Parallel upload workers (default: 5)
262
+ ```
263
+
264
+ The complete manifest is staged in small, portable command-line chunks after all uploads finish. One
265
+ final mutation atomically publishes the manifest and deployment settings. Replaced files stay in
266
+ component storage for at least seven days. A request for a missing file with an extension, except
267
+ HTML, gets the newest replaced copy, so pages that are already open can still load their scripts.
268
+ Files remain available until a later upload removes expired files in bounded cleanup transactions.
269
+ Legacy CDN blobs are not retained. A failed attempt leaves the previous deployment live and removes
270
+ only new files that are not referenced by the live manifest. The supported maximum is 1,800 files
271
+ and 2 MiB of serialized manifest metadata, which keeps the atomic switch below Convex transaction
272
+ limits. Upload URLs are generated in bounded batches. Later uploads recover component files and
273
+ staging records left unreferenced for more than 24 hours after an interrupted CLI.
274
+
275
+ Convex HTTP routers currently expose GET but not HEAD routes. Uptime monitors must use a lightweight
276
+ GET request.
277
+
278
+ ## Mounting under a sub-path
279
+
280
+ To mount under `/app/` (for example, if you have other HTTP routes at the root):
281
+
282
+ ```typescript
283
+ app.use(hosting, { httpPrefix: "/app/" });
284
+ ```
285
+
286
+ The bundler also needs to know the base path so the emitted HTML references the right URLs. The CLI
287
+ sets a `CONVEX_HOSTING_BASE_PATH` env var matching the component's mount when it runs your build, so
288
+ `vite.config.ts` can read it:
289
+
290
+ ```typescript
291
+ import { defineConfig } from "vite";
292
+
293
+ export default defineConfig({
294
+ base: process.env.CONVEX_HOSTING_BASE_PATH ?? "/",
295
+ });
296
+ ```
297
+
298
+ Root-mounted apps don't need this; the default is `/`. Webpack/Next.js equivalents: `publicPath` and
299
+ `assetPrefix`.
300
+
301
+ ## SPA routing
302
+
303
+ Requests for an extension-less path that doesn't match an uploaded file fall back to `index.html`,
304
+ so client-side routes survive a reload. Paths with an extension (e.g. `/missing.js`) always 404 when
305
+ not found. To turn the fallback off for a multi-page app (unknown paths become real 404s), deploy
306
+ with `--no-spa`:
307
+
308
+ ```bash
309
+ npx @samebase/convex-hosting deploy --no-spa
310
+ # or: npx @samebase/convex-hosting upload --no-spa
311
+ ```
312
+
313
+ The flag is stored on the deployment record (the `deploymentInfo` table), so the serving behavior
314
+ travels with the code you ship rather than living in a separate env var. Re-deploy without
315
+ `--no-spa` or with `--spa` to turn it back on.
316
+
317
+ ## Switching from @convex-dev/static-hosting
318
+
319
+ Follow
320
+ [Switching from @convex-dev/static-hosting](./README.md#switching-from-convex-devstatic-hosting) in
321
+ the README. Mount the component with `{ name: "staticHosting" }` to keep the uploaded files and
322
+ URLs. An app on 0.1.x first follows upstream's
323
+ [0.1.x to 0.2.x migration guide](https://github.com/get-convex/static-hosting/blob/main/MIGRATION.md).
324
+
325
+ ## Troubleshooting
326
+
327
+ ### 404s on every path
328
+
329
+ Run `npx convex dev` (or `npx convex deploy`) after adding the component. In component-owned mode,
330
+ verify its `httpPrefix`. In app-owned mode, verify that `registerStaticRoutes` is called from
331
+ `convex/http.ts` and the component itself has no `httpPrefix`.
332
+
333
+ ### Wrong `VITE_CONVEX_URL` in the built bundle
334
+
335
+ ```bash
336
+ # Right: CLI sets VITE_CONVEX_URL for the target deployment
337
+ npx @samebase/convex-hosting deploy
338
+
339
+ # Wrong: uses VITE_CONVEX_URL from .env.local
340
+ npm run build && npx @samebase/convex-hosting upload --prod
341
+ ```
342
+
343
+ ### "Cannot find module convex.config"
344
+
345
+ Make sure you've installed the package and it's listed in `package.json`:
346
+
347
+ ```bash
348
+ npm install @samebase/convex-hosting
349
+ ```
350
+
351
+ ### Component name mismatch
352
+
353
+ If you've renamed the component instance with `app.use(hosting, { name: "custom" })`, pass it to the
354
+ CLI and replace every generated `components.hosting` reference with `components.custom`:
355
+
356
+ ```bash
357
+ npx @samebase/convex-hosting upload --component custom
358
+ ```
359
+
360
+ ## API Reference
361
+
362
+ ### `registerStaticRoutes(http, component, options?)`
363
+
364
+ Registers a static catch-all in the app's `convex/http.ts`. Use it when exact app routes must remain
365
+ at the same root. `pathPrefix`, `spaFallback`, and `cdnBaseUrl` are supported. The component must be
366
+ installed without an `httpPrefix` so only the app owns that URL space.
367
+
368
+ ### `serveStaticAsset(ctx, component, request, options?)`
369
+
370
+ Serves an uploaded file from an app-owned HTTP action, or returns `null` when no file matches so the
371
+ action can continue with its own routing (server rendering, prerendered pages, redirects). Responses
372
+ match `registerStaticRoutes`. The lookup is exact: `/` is not mapped to `/index.html`, and
373
+ `spaFallback` defaults to `false`. `path` (the decoded file path, defaulting to the decoded request
374
+ path, starting with `/` and without your route's prefix), `spaFallback`, and `cdnBaseUrl` are
375
+ supported. A request path with malformed percent-encoding returns `null`. A found file whose storage
376
+ can't be read gets a 500 response. The component must be installed without an `httpPrefix`.
377
+
378
+ ### `decodeRequestPath(pathname)`
379
+
380
+ Decodes a URL pathname the way the static handlers do. Returns `null` for malformed
381
+ percent-encoding.
382
+
383
+ ### `exposeDeploymentQuery(component)`
384
+
385
+ Returns `{ getCurrentDeployment }`: a public query that wraps the component's deployment singleton.
386
+ Add this only if you use `<UpdateBanner />` or `useDeploymentUpdates`.
387
+
388
+ ### `getConvexUrl()`
389
+
390
+ Browser-only. Returns `https://<deployment>.convex.cloud` when the page is served from
391
+ `<deployment>.convex.site`.
392
+
393
+ ## Additional Resources
394
+
395
+ - [README](./README.md)
396
+ - [Upstream 0.1.x to 0.2.x migration guide](https://github.com/get-convex/static-hosting/blob/main/MIGRATION.md)
397
+ - [`examples/spa`](https://github.com/samebase/convex-hosting/tree/main/examples/spa): Working
398
+ example app
399
+ - [Component source](https://github.com/samebase/convex-hosting/tree/main/packages/convex-hosting/src/component)