@samebase/convex-hosting 0.3.1 → 0.4.0

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 (193) hide show
  1. package/CHANGELOG.md +10 -128
  2. package/README.md +136 -398
  3. package/dist/cli/convex.d.ts +63 -0
  4. package/dist/cli/convex.js +108 -0
  5. package/dist/cli/convex.js.map +1 -0
  6. package/dist/cli/files.d.ts +18 -0
  7. package/dist/cli/files.js +106 -0
  8. package/dist/cli/files.js.map +1 -0
  9. package/dist/cli/index.d.ts +1 -11
  10. package/dist/cli/index.js +3 -58
  11. package/dist/cli/index.js.map +1 -1
  12. package/dist/cli/main.d.ts +5 -0
  13. package/dist/cli/main.js +346 -0
  14. package/dist/cli/main.js.map +1 -0
  15. package/dist/client/index.d.ts +8 -115
  16. package/dist/client/index.js +65 -254
  17. package/dist/client/index.js.map +1 -1
  18. package/dist/client/types.d.ts +5 -0
  19. package/dist/client/version-module.d.ts +6 -0
  20. package/dist/component/_generated/api.d.ts +0 -5
  21. package/dist/component/_generated/api.js.map +1 -1
  22. package/dist/component/_generated/component.d.ts +20 -18
  23. package/dist/component/_generated/dataModel.d.ts +0 -1
  24. package/dist/component/_generated/server.d.ts +12 -1
  25. package/dist/component/_generated/server.js +2 -0
  26. package/dist/component/_generated/server.js.map +1 -1
  27. package/dist/component/convex.config.d.ts +0 -1
  28. package/dist/component/lib.d.ts +175 -156
  29. package/dist/component/lib.js +396 -710
  30. package/dist/component/lib.js.map +1 -1
  31. package/dist/component/schema.d.ts +76 -72
  32. package/dist/component/schema.js +35 -59
  33. package/dist/component/schema.js.map +1 -1
  34. package/dist/workers-shared/asset-worker/src/analytics.d.ts +39 -0
  35. package/dist/workers-shared/asset-worker/src/analytics.js +86 -0
  36. package/dist/workers-shared/asset-worker/src/analytics.js.map +1 -0
  37. package/dist/workers-shared/asset-worker/src/compatibility-flags.d.ts +19 -0
  38. package/dist/workers-shared/asset-worker/src/compatibility-flags.js +29 -0
  39. package/dist/workers-shared/asset-worker/src/compatibility-flags.js.map +1 -0
  40. package/dist/workers-shared/asset-worker/src/configuration.d.ts +3 -0
  41. package/dist/workers-shared/asset-worker/src/configuration.js +30 -0
  42. package/dist/workers-shared/asset-worker/src/configuration.js.map +1 -0
  43. package/dist/workers-shared/asset-worker/src/constants.d.ts +1 -0
  44. package/dist/workers-shared/asset-worker/src/constants.js +3 -0
  45. package/dist/workers-shared/asset-worker/src/constants.js.map +1 -0
  46. package/dist/workers-shared/asset-worker/src/handler.d.ts +28 -0
  47. package/dist/workers-shared/asset-worker/src/handler.js +717 -0
  48. package/dist/workers-shared/asset-worker/src/handler.js.map +1 -0
  49. package/dist/workers-shared/asset-worker/src/types.d.ts +13 -0
  50. package/dist/workers-shared/asset-worker/src/types.js +2 -0
  51. package/dist/workers-shared/asset-worker/src/types.js.map +1 -0
  52. package/dist/workers-shared/asset-worker/src/utils/base-path.d.ts +29 -0
  53. package/dist/workers-shared/asset-worker/src/utils/base-path.js +56 -0
  54. package/dist/workers-shared/asset-worker/src/utils/base-path.js.map +1 -0
  55. package/dist/workers-shared/asset-worker/src/utils/headers.d.ts +11 -0
  56. package/dist/workers-shared/asset-worker/src/utils/headers.js +77 -0
  57. package/dist/workers-shared/asset-worker/src/utils/headers.js.map +1 -0
  58. package/dist/workers-shared/asset-worker/src/utils/rules-engine.d.ts +23 -0
  59. package/dist/workers-shared/asset-worker/src/utils/rules-engine.js +133 -0
  60. package/dist/workers-shared/asset-worker/src/utils/rules-engine.js.map +1 -0
  61. package/dist/workers-shared/asset-worker/src/worker.d.ts +14 -0
  62. package/dist/workers-shared/asset-worker/src/worker.js +12 -0
  63. package/dist/workers-shared/asset-worker/src/worker.js.map +1 -0
  64. package/dist/workers-shared/utils/base-path.d.ts +32 -0
  65. package/dist/workers-shared/utils/base-path.js +94 -0
  66. package/dist/workers-shared/utils/base-path.js.map +1 -0
  67. package/dist/workers-shared/utils/configuration/constants.d.ts +15 -0
  68. package/dist/workers-shared/utils/configuration/constants.js +16 -0
  69. package/dist/workers-shared/utils/configuration/constants.js.map +1 -0
  70. package/dist/workers-shared/utils/configuration/constructConfiguration.d.ts +12 -0
  71. package/dist/workers-shared/utils/configuration/constructConfiguration.js +103 -0
  72. package/dist/workers-shared/utils/configuration/constructConfiguration.js.map +1 -0
  73. package/dist/workers-shared/utils/configuration/parseHeaders.d.ts +5 -0
  74. package/dist/workers-shared/utils/configuration/parseHeaders.js +181 -0
  75. package/dist/workers-shared/utils/configuration/parseHeaders.js.map +1 -0
  76. package/dist/workers-shared/utils/configuration/parseRedirects.d.ts +8 -0
  77. package/dist/workers-shared/utils/configuration/parseRedirects.js +129 -0
  78. package/dist/workers-shared/utils/configuration/parseRedirects.js.map +1 -0
  79. package/dist/workers-shared/utils/configuration/parseStaticRouting.d.ts +2 -0
  80. package/dist/workers-shared/utils/configuration/parseStaticRouting.js +94 -0
  81. package/dist/workers-shared/utils/configuration/parseStaticRouting.js.map +1 -0
  82. package/dist/workers-shared/utils/configuration/types.d.ts +38 -0
  83. package/dist/workers-shared/utils/configuration/types.js +2 -0
  84. package/dist/workers-shared/utils/configuration/types.js.map +1 -0
  85. package/dist/workers-shared/utils/configuration/validateURL.d.ts +3 -0
  86. package/dist/workers-shared/utils/configuration/validateURL.js +56 -0
  87. package/dist/workers-shared/utils/configuration/validateURL.js.map +1 -0
  88. package/dist/workers-shared/utils/constants.d.ts +28 -0
  89. package/dist/workers-shared/utils/constants.js +34 -0
  90. package/dist/workers-shared/utils/constants.js.map +1 -0
  91. package/dist/workers-shared/utils/helpers.d.ts +22 -0
  92. package/dist/workers-shared/utils/helpers.js +73 -0
  93. package/dist/workers-shared/utils/helpers.js.map +1 -0
  94. package/dist/workers-shared/utils/responses.d.ts +43 -0
  95. package/dist/workers-shared/utils/responses.js +125 -0
  96. package/dist/workers-shared/utils/responses.js.map +1 -0
  97. package/dist/workers-shared/utils/tracing.d.ts +3 -0
  98. package/dist/workers-shared/utils/tracing.js +29 -0
  99. package/dist/workers-shared/utils/tracing.js.map +1 -0
  100. package/dist/workers-shared/utils/types.d.ts +131 -0
  101. package/dist/workers-shared/utils/types.js +72 -0
  102. package/dist/workers-shared/utils/types.js.map +1 -0
  103. package/package.json +16 -32
  104. package/INTEGRATION.md +0 -399
  105. package/dist/cli/args.d.ts +0 -28
  106. package/dist/cli/args.d.ts.map +0 -1
  107. package/dist/cli/args.js +0 -148
  108. package/dist/cli/args.js.map +0 -1
  109. package/dist/cli/argumentChunks.d.ts +0 -3
  110. package/dist/cli/argumentChunks.d.ts.map +0 -1
  111. package/dist/cli/argumentChunks.js +0 -29
  112. package/dist/cli/argumentChunks.js.map +0 -1
  113. package/dist/cli/collectFiles.d.ts +0 -10
  114. package/dist/cli/collectFiles.d.ts.map +0 -1
  115. package/dist/cli/collectFiles.js +0 -53
  116. package/dist/cli/collectFiles.js.map +0 -1
  117. package/dist/cli/commands.d.ts +0 -8
  118. package/dist/cli/commands.d.ts.map +0 -1
  119. package/dist/cli/commands.js +0 -56
  120. package/dist/cli/commands.js.map +0 -1
  121. package/dist/cli/componentName.d.ts +0 -32
  122. package/dist/cli/componentName.d.ts.map +0 -1
  123. package/dist/cli/componentName.js +0 -43
  124. package/dist/cli/componentName.js.map +0 -1
  125. package/dist/cli/deploy.d.ts +0 -16
  126. package/dist/cli/deploy.d.ts.map +0 -1
  127. package/dist/cli/deploy.js +0 -283
  128. package/dist/cli/deploy.js.map +0 -1
  129. package/dist/cli/deployEnvironment.d.ts +0 -10
  130. package/dist/cli/deployEnvironment.d.ts.map +0 -1
  131. package/dist/cli/deployEnvironment.js +0 -12
  132. package/dist/cli/deployEnvironment.js.map +0 -1
  133. package/dist/cli/index.d.ts.map +0 -1
  134. package/dist/cli/upload.d.ts +0 -22
  135. package/dist/cli/upload.d.ts.map +0 -1
  136. package/dist/cli/upload.js +0 -630
  137. package/dist/cli/upload.js.map +0 -1
  138. package/dist/client/_generated/_ignore.d.ts +0 -1
  139. package/dist/client/_generated/_ignore.d.ts.map +0 -1
  140. package/dist/client/_generated/_ignore.js +0 -3
  141. package/dist/client/_generated/_ignore.js.map +0 -1
  142. package/dist/client/index.d.ts.map +0 -1
  143. package/dist/component/_generated/api.d.ts.map +0 -1
  144. package/dist/component/_generated/component.d.ts.map +0 -1
  145. package/dist/component/_generated/dataModel.d.ts.map +0 -1
  146. package/dist/component/_generated/server.d.ts.map +0 -1
  147. package/dist/component/convex.config.d.ts.map +0 -1
  148. package/dist/component/http.d.ts +0 -4
  149. package/dist/component/http.d.ts.map +0 -1
  150. package/dist/component/http.js +0 -125
  151. package/dist/component/http.js.map +0 -1
  152. package/dist/component/lib.d.ts.map +0 -1
  153. package/dist/component/schema.d.ts.map +0 -1
  154. package/dist/component/serving.d.ts +0 -17
  155. package/dist/component/serving.d.ts.map +0 -1
  156. package/dist/component/serving.js +0 -150
  157. package/dist/component/serving.js.map +0 -1
  158. package/dist/react/index.d.ts +0 -59
  159. package/dist/react/index.d.ts.map +0 -1
  160. package/dist/react/index.js +0 -137
  161. package/dist/react/index.js.map +0 -1
  162. package/src/cli/args.test.ts +0 -79
  163. package/src/cli/args.ts +0 -153
  164. package/src/cli/argumentChunks.test.ts +0 -27
  165. package/src/cli/argumentChunks.ts +0 -40
  166. package/src/cli/collectFiles.test.ts +0 -26
  167. package/src/cli/collectFiles.ts +0 -59
  168. package/src/cli/commands.ts +0 -68
  169. package/src/cli/componentName.test.ts +0 -41
  170. package/src/cli/componentName.ts +0 -46
  171. package/src/cli/deploy.ts +0 -336
  172. package/src/cli/deployEnvironment.test.ts +0 -27
  173. package/src/cli/deployEnvironment.ts +0 -17
  174. package/src/cli/index.ts +0 -65
  175. package/src/cli/upload.ts +0 -837
  176. package/src/client/_generated/_ignore.ts +0 -1
  177. package/src/client/index.test.ts +0 -510
  178. package/src/client/index.ts +0 -355
  179. package/src/client/setup.test.ts +0 -22
  180. package/src/component/_generated/api.ts +0 -54
  181. package/src/component/_generated/component.ts +0 -55
  182. package/src/component/_generated/dataModel.ts +0 -60
  183. package/src/component/_generated/server.ts +0 -156
  184. package/src/component/convex.config.ts +0 -3
  185. package/src/component/http.test.ts +0 -223
  186. package/src/component/http.ts +0 -144
  187. package/src/component/lib.test.ts +0 -1007
  188. package/src/component/lib.ts +0 -862
  189. package/src/component/schema.ts +0 -75
  190. package/src/component/serving.ts +0 -158
  191. package/src/component/setup.test.ts +0 -11
  192. package/src/react/index.tsx +0 -197
  193. package/src/test.ts +0 -23
package/README.md CHANGED
@@ -1,89 +1,20 @@
1
1
  # Convex Hosting
2
2
 
3
- [![npm version](https://badge.fury.io/js/@samebase%2Fconvex-hosting.svg)](https://badge.fury.io/js/@samebase/convex-hosting)
3
+ Host a web app on Convex, next to its backend: the files of a build and, when the app renders pages,
4
+ those pages. The site is at `https://<deployment>.convex.site`.
4
5
 
5
- A Convex component for hosting static React/Vite apps directly on Convex: no separate hosting
6
- provider, no DNS to wire up, no second deploy target. Run one command and your frontend is live at
7
- `https://<deployment>.convex.site` alongside your backend. For server-rendered TanStack Start pages,
8
- add
9
- [`@samebase/convex-hosting-tanstack-start`](https://github.com/samebase/convex-hosting/tree/main/packages/convex-hosting-tanstack-start).
6
+ It works like
7
+ [Cloudflare Workers with static assets](https://developers.cloudflare.com/workers/static-assets/):
8
+ the same `_headers`, `_redirects`, and `.assetsignore` files, the same `html_handling` and
9
+ `not_found_handling` options, and the same responses, computed by Cloudflare's own asset-serving
10
+ code.
10
11
 
11
- This is Samebase's fork of [get-convex/static-hosting](https://github.com/get-convex/static-hosting)
12
- (Apache-2.0). It is not affiliated with or endorsed by Convex.
12
+ ## Install
13
13
 
14
- ## Features
15
-
16
- - 🚀 **One-command deploy:** build, push backend, and upload static files in a single step.
17
- - 🔄 **SPA routing:** paths without an extension fall back to `index.html`.
18
- - ⚡ **Smart caching:** static files are cached for speed, with safe updates when a new version is
19
- deployed.
20
- - 🔔 **Deployment update notifications:** show connected users a prompt when a new version is ready.
21
- - 🔒 **Authenticated uploads:** uploads go through the Convex CLI's authenticated session; there's
22
- no public upload endpoint.
23
- - 🧹 **Automatic cleanup:** replaced non-HTML files stay available for at least seven days, so open
24
- pages keep working, and a later deploy removes them.
25
-
26
- ## Quick Start
27
-
28
- > **Coming from `@convex-dev/static-hosting`?** See
29
- > [Switching from @convex-dev/static-hosting](#switching-from-convex-devstatic-hosting).
30
-
31
- Follow the three [setup steps](#setup), then run:
32
-
33
- ```bash
34
- npm run deploy
35
- ```
36
-
37
- Your app is live at `https://<deployment>.convex.site`.
38
-
39
- ## Setup
40
-
41
- ### 1. Install
42
-
43
- ```bash
14
+ ```sh
44
15
  npm install @samebase/convex-hosting
45
16
  ```
46
17
 
47
- ### 2. Register the component
48
-
49
- `convex/convex.config.ts`:
50
-
51
- ```ts
52
- import { defineApp } from "convex/server";
53
- import hosting from "@samebase/convex-hosting/convex.config";
54
-
55
- // Your own HTTP endpoints (convex/http.ts) are served under /api so the
56
- // static site can own the root.
57
- const app = defineApp({ httpPrefix: "/api" });
58
- app.use(hosting, { httpPrefix: "/" });
59
-
60
- export default app;
61
- ```
62
-
63
- This is the fastest serving mode. The component owns `/`, while your app's `convex/http.ts` routes
64
- move under `/api/...`.
65
-
66
- If existing callbacks or auth routes must stay at the root, use
67
- [app-owned root routing](#keep-existing-http-routes-at-the-root). To host the site under a sub-path,
68
- see [Mounting under a sub-path](#mounting-under-a-sub-path).
69
-
70
- ### 3. Add a deploy script
71
-
72
- ```json
73
- {
74
- "scripts": {
75
- "deploy": "npx @samebase/convex-hosting deploy"
76
- }
77
- }
78
- ```
79
-
80
- That's it.
81
-
82
- ### Keep existing HTTP routes at the root
83
-
84
- Do not move stable webhook, auth, or API URLs just to add hosting. Leave the component's HTTP routes
85
- unmounted and register the static catch-all in your existing router.
86
-
87
18
  `convex/convex.config.ts`:
88
19
 
89
20
  ```ts
@@ -91,382 +22,189 @@ import { defineApp } from "convex/server";
91
22
  import hosting from "@samebase/convex-hosting/convex.config";
92
23
 
93
24
  const app = defineApp();
94
- app.use(hosting); // no httpPrefix
25
+ app.use(hosting);
95
26
 
96
27
  export default app;
97
28
  ```
98
29
 
99
- `convex/http.ts`:
30
+ `convex/http.ts` has one `GET` route for the site. Convex answers `HEAD` through it.
100
31
 
101
32
  ```ts
102
33
  import { httpRouter } from "convex/server";
103
- import { registerStaticRoutes } from "@samebase/convex-hosting";
104
- import { components } from "./_generated/api";
34
+ import { serveAsset } from "@samebase/convex-hosting";
35
+ import { components } from "./_generated/api.js";
36
+ import { httpAction } from "./_generated/server.js";
37
+ import { versionId } from "../.convex-hosting/version.js";
105
38
 
106
39
  const http = httpRouter();
107
40
 
108
- // Register exact app routes first. Existing auth/webhook helpers can stay here.
109
- // auth.addHttpRoutes(http);
110
- registerStaticRoutes(http, components.hosting);
41
+ http.route({
42
+ pathPrefix: "/",
43
+ method: "GET",
44
+ handler: httpAction(async (ctx, request) => {
45
+ const asset = await serveAsset(ctx, components.hosting, request, versionId);
46
+ if (asset) return asset;
47
+ // A static site: 404. A server-rendered site: render the page here, also with `no-store`.
48
+ // The deployment's Cloudflare front caches answers on static-looking paths.
49
+ return new Response(null, { status: 404, headers: { "Cache-Control": "no-store" } });
50
+ }),
51
+ });
111
52
 
112
53
  export default http;
113
54
  ```
114
55
 
115
- Exact routes win over the static catch-all, so existing URLs keep working. The component still owns
116
- uploads, deployment state, and file storage. This mode adds an internal query and storage fetch on
117
- an uncached request, so prefer the component-owned mode when you do not need root-level app routes.
118
-
119
- ### Using non-Vite bundlers
120
-
121
- The `deploy` build step and `upload --build` set `VITE_CONVEX_URL`. For bundlers that use different
122
- environment variable conventions, wrap your build script to pass through the value:
123
-
124
- **For Expo:**
125
-
126
- ```json
127
- {
128
- "scripts": {
129
- "build": "EXPO_PUBLIC_CONVEX_URL=${VITE_CONVEX_URL:-$EXPO_PUBLIC_CONVEX_URL} npx expo export --platform web"
130
- }
131
- }
132
- ```
133
-
134
- **For Next.js:**
135
-
136
- ```json
137
- {
138
- "scripts": {
139
- "build": "NEXT_PUBLIC_CONVEX_URL=${VITE_CONVEX_URL:-$NEXT_PUBLIC_CONVEX_URL} next build"
140
- }
141
- }
142
- ```
143
-
144
- The pattern `${VITE_CONVEX_URL:-$VAR}` uses `VITE_CONVEX_URL` if set by the CLI and otherwise falls
145
- back to your bundler-specific variable. This keeps both the CLI-driven build and standalone
146
- `npm run build` working.
147
-
148
- ## Deployment
56
+ `convex-hosting` keeps its state in `.convex-hosting/` at the project root, next to `convex/`, like
57
+ Wrangler's `.wrangler/`. Every deploy writes the version to `.convex-hosting/version.js`. The folder
58
+ has its own `.gitignore`, so git ignores it. The package declares the module's type, so a fresh
59
+ clone typechecks before its first deploy; nothing to add.
149
60
 
150
- Deploy both Convex backend and static files with a single command:
61
+ ## Deploy
151
62
 
152
- ```bash
153
- npx convex login # first time only
154
- npx @samebase/convex-hosting deploy
63
+ ```sh
64
+ npx convex-hosting deploy --build "vite build" --dist dist --not-found-handling single-page-application
155
65
  ```
156
66
 
157
- The `deploy` command:
158
-
159
- 1. Builds your frontend with the production `VITE_CONVEX_URL`.
160
- 2. Deploys the Convex backend.
161
- 3. Uploads `dist/` to Convex. It skips `_headers` and `_redirects` at the root of `dist/`:
162
- Cloudflare and Netlify read these files as hosting rules, and this package does not apply them.
163
-
164
- For more control, you can run the two halves separately:
67
+ The build command runs in the project directory. Do not change directory in it.
165
68
 
166
- ```bash
167
- npx convex deploy
168
- npx @samebase/convex-hosting upload --build --prod
169
- ```
170
-
171
- Your app is live at `https://<deployment>.convex.site`.
69
+ `deploy` runs `npx convex deploy` with your build and the upload of `--dist` as its `--cmd`. The
70
+ pushed code names the version it was built with, so the push switches the site, and the command
71
+ ends. The first request that the new code serves marks its version live, which deletes the versions
72
+ that were live before it. A version that never becomes live, from a deploy that failed, is deleted
73
+ when it is a day old, with the stored files that no version uses.
172
74
 
173
- ## Development workflow
75
+ `deploy` goes to production. `--preview-name <name>` sends it to that preview (with a login or a
76
+ project's preview deploy key; always pass the name with that key), and `--dev` to the development
77
+ deployment (see [Development](#development)). A deploy key in `CONVEX_DEPLOY_KEY` or
78
+ `CONVEX_DEPLOYMENT_TOKEN` (from the environment or `.env.local`) selects its own deployment instead,
79
+ and so does `CONVEX_SELF_HOSTED_URL` for a self-hosted deployment.
174
80
 
175
- Use your normal frontend dev server during development:
81
+ The first deploy to a deployment pushes the component, then uploads the files. Until that upload
82
+ ends, the site answers 404.
176
83
 
177
- ```bash
178
- # Terminal 1
179
- npx convex dev
84
+ Two deploys that overlap both complete, and the last push wins.
180
85
 
181
- # Terminal 2
182
- npm run dev
183
- ```
86
+ ## Development
184
87
 
185
- For Vite, that keeps HMR and fast local feedback. The hosting component is the deploy target, not a
186
- replacement dev server. Uploading every edit to a development deployment is slower and loses HMR,
187
- even when an agent writes most of the code. Humans still need the quick loop for visual checks,
188
- transient UI state, and debugging.
88
+ The inner loop is your framework's dev server, such as Vite's, which serves the frontend locally
89
+ with hot reload against the development deployment's URL from `.env.local`, plus `npx convex dev`,
90
+ which pushes your function changes.
189
91
 
190
- Before release, run one hosted smoke test against the development deployment:
92
+ When the development deployment should serve the current build, run the one-shot command:
191
93
 
192
- ```bash
193
- npx @samebase/convex-hosting upload --build
94
+ ```sh
95
+ npx convex-hosting deploy --dev --build "vite build" --dist dist --not-found-handling single-page-application
194
96
  ```
195
97
 
196
- Then use `deploy` for production. This split keeps the dev loop fast while still testing the real
197
- HTTP, caching, base-path, and SPA behavior before shipping.
198
-
199
- ## CLI options
200
-
201
- ```bash
202
- npx @samebase/convex-hosting deploy [options]
203
- -d, --dist <path> Path to dist directory (default: ./dist)
204
- -c, --component <name> Component instance name (default: hosting)
205
- --skip-build Skip the build step (use existing dist)
206
- --skip-convex Skip Convex backend deployment
207
- --build-command <cmd> Build command to run (default: 'npm run build')
208
- --no-spa Disable SPA fallback (404 instead of /index.html)
209
- --spa Enable SPA fallback (default)
210
- --cdn Use the legacy convex-fs integration
211
- --cdn-delete-function Legacy app function that deletes CDN blobs
212
-
213
- npx @samebase/convex-hosting upload [options]
214
- -d, --dist <path> Path to dist directory (default: ./dist)
215
- -c, --component <name> Component instance name (default: hosting)
216
- --prod Deploy to production deployment
217
- --preview-name <name> Upload to the named preview deployment
218
- -b, --build Run 'npm run build' with VITE_CONVEX_URL set
219
- --build-command <cmd> Override the build command; implies --build
220
- --no-spa Disable SPA fallback (404 instead of /index.html)
221
- --spa Enable SPA fallback (default)
222
- --cdn Use the legacy convex-fs integration
223
- --cdn-delete-function Legacy app function that deletes CDN blobs
224
- -j, --concurrency <n> Parallel upload workers (default: 5)
225
- ```
98
+ `deploy --dev` goes to the development deployment of this checkout, the one in `.env.local`. It runs
99
+ the build itself, uploads `--dist`, writes `.convex-hosting/version.js`, and pushes with
100
+ `npx convex run --push`, which works whether or not `npx convex dev` runs. The build runs with your
101
+ environment, so the framework reads the deployment's URL from `.env.local` (Vite reads
102
+ `VITE_CONVEX_URL`). Against an anonymous local deployment, `deploy --dev` needs `npx convex dev`
103
+ running, because without it every Convex CLI call starts and stops its own backend and the upload's
104
+ requests find nothing; cloud development deployments do not have this constraint. There is no watch
105
+ mode: a build, an upload, and a push per change is not an inner loop, and Cloudflare's own tooling
106
+ works the same way, with a local dev server and a push on demand. A fresh clone runs `deploy --dev`
107
+ once; after that, the version file exists and `npx convex dev` bundles as usual.
226
108
 
227
- `--preview-name` selects the deployment the same way as `npx convex run --preview-name`, so a dev or
228
- production `CONVEX_DEPLOY_KEY` takes precedence over it.
109
+ Two cases are transient:
229
110
 
230
- Each upload is published atomically, so visitors never see a page that refers to assets that are not
231
- available yet. Failed uploads leave the previous deployment live. Replaced non-HTML files stay
232
- available for at least seven days, so pages that are already open can still load their scripts. A
233
- later upload removes expired files. See [INTEGRATION.md](./INTEGRATION.md) for upload limits and
234
- lifecycle details.
111
+ - A server-rendered app imports `dist/server` in `convex/http.ts`, so a running `npx convex dev`
112
+ pushes after the build, before `deploy --dev` writes the new version. Until `deploy --dev` ends,
113
+ the new pages refer to files that answer 404.
114
+ - A production deploy writes production's version to the version file, and a running
115
+ `npx convex dev` pushes it to the development deployment, where that version does not exist, until
116
+ the next `deploy --dev`. Run production deploys in CI or in a separate checkout.
235
117
 
236
- Convex HTTP routes currently support GET but not HEAD. Configure uptime checks to make a lightweight
237
- GET request rather than a HEAD request.
118
+ ## Options
238
119
 
239
- If you mount the component under a different name with `app.use(hosting, { name: "custom" })`, pass
240
- `--component custom` and replace every generated `components.hosting` reference with
241
- `components.custom`.
120
+ | Option | Values |
121
+ | ------------------------------ | -------------------------------------------------------------------------------------- |
122
+ | `--dev` | The development deployment of this checkout, instead of production |
123
+ | `--preview-name <name>` | The preview deployment with this name, instead of production |
124
+ | `--html-handling <value>` | `auto-trailing-slash` (default), `force-trailing-slash`, `drop-trailing-slash`, `none` |
125
+ | `--not-found-handling <value>` | `none` (default), `single-page-application`, `404-page` |
126
+ | `--component <name>` | The component instance, `hosting` by default |
242
127
 
243
- Do not use `--cdn` for a new integration. It targets an older ConvexFS HTTP API and is retained only
244
- for existing deployments. Legacy CDN users must keep app-owned root routing because `/fs/upload` and
245
- `/fs/blobs/*` are root app routes. See `INTEGRATION.md` for the current limitation.
128
+ The two handling options work as
129
+ [Cloudflare documents them](https://developers.cloudflare.com/workers/static-assets/routing/).
246
130
 
247
- ## Security
131
+ Each file of `--dist` must be 25 MiB or smaller, and the CLI names a file that is larger. This is
132
+ Cloudflare's limit per asset, and the HTTP action that serves a file cannot answer more than
133
+ Convex's 100 MiB response limit anyway.
248
134
 
249
- The upload API uses **internal functions** in the Component that can only be called via:
135
+ ## `_headers`, `_redirects`, and `.assetsignore`
250
136
 
251
- - `npx convex run` (requires Convex CLI authentication)
252
- - Other Convex functions in the Component (server-side only)
137
+ Put them at the root of `--dist`, as for Cloudflare:
138
+ [`_headers`](https://developers.cloudflare.com/workers/static-assets/headers/),
139
+ [`_redirects`](https://developers.cloudflare.com/workers/static-assets/redirects/), and
140
+ [`.assetsignore`](https://developers.cloudflare.com/workers/static-assets/binding/#ignoring-assets).
141
+ They are not served. Symbolic links in `--dist` are followed, as Wrangler follows them, also when
142
+ they point outside the folder; a link to nothing, or back into a folder already included, is skipped
143
+ with a message. `.assetsignore` excludes what a link should not publish.
253
144
 
254
- This means unauthorized users **cannot** upload files to your site, even if they know your Convex
255
- URL.
145
+ Every asset has `Cache-Control: public, max-age=0, must-revalidate` and an `ETag`. To cache hashed
146
+ files for a year, add a `_headers` rule:
256
147
 
257
- ## Reload prompt after deploy (optional)
258
-
259
- If you want a banner that prompts users to reload when a new deployment ships, expose the deployment
260
- query in your app and drop in `<UpdateBanner />`:
261
-
262
- `convex/hosting.ts`:
263
-
264
- ```ts
265
- import { exposeDeploymentQuery } from "@samebase/convex-hosting";
266
- import { components } from "./_generated/api";
267
-
268
- export const { getCurrentDeployment } = exposeDeploymentQuery(components.hosting);
269
148
  ```
270
-
271
- `src/App.tsx`:
272
-
273
- ```tsx
274
- import { UpdateBanner } from "@samebase/convex-hosting/react";
275
-
276
- function App() {
277
- return (
278
- <>
279
- <UpdateBanner message="New version!" buttonText="Reload" />
280
- {/* ... */}
281
- </>
282
- );
283
- }
284
- ```
285
-
286
- `UpdateBanner` resolves `api.hosting.getCurrentDeployment` automatically. If you re-export the query
287
- under a different module name, pass it explicitly:
288
-
289
- ```tsx
290
- import { api } from "../convex/_generated/api";
291
- <UpdateBanner getCurrentDeployment={api.myModule.getCurrentDeployment} />;
292
- ```
293
-
294
- For custom UI, use the hook:
295
-
296
- ```tsx
297
- import { useDeploymentUpdates } from "@samebase/convex-hosting/react";
298
-
299
- const { updateAvailable, reload, dismiss } = useDeploymentUpdates();
300
- ```
301
-
302
- ## Mounting under a sub-path
303
-
304
- Mount the static site under a sub-path if you have other routes at the root:
305
-
306
- ```ts
307
- app.use(hosting, { httpPrefix: "/app/" });
149
+ /assets/*
150
+ Cache-Control: public, max-age=31536000, immutable
308
151
  ```
309
152
 
310
- You'll also need to tell your bundler about the base path so the emitted HTML references the right
311
- URLs. The CLI sets a `CONVEX_HOSTING_BASE_PATH` env var matching the component's mount when it runs
312
- your build, so `vite.config.ts` can read it directly:
313
-
314
- ```ts
315
- import { defineConfig } from "vite";
316
-
317
- export default defineConfig({
318
- base: process.env.CONVEX_HOSTING_BASE_PATH ?? "/",
319
- });
320
- ```
153
+ ## Routing
321
154
 
322
- Root-mounted apps don't need this; the default is `/`. For webpack use `publicPath`, for Next.js
323
- `assetPrefix`.
155
+ `serveAsset` returns the asset's response, or `null` when no asset matches, so your route continues.
156
+ When the version does not exist or is still uploading, it returns a 404 response that names the
157
+ version. At the first request for a new version, it marks the version live.
324
158
 
325
- ## SPA routing
159
+ `not_found_handling` applies to navigation requests (`Sec-Fetch-Mode: navigate`) only, as on
160
+ Cloudflare when a Worker runs: other requests that match no file reach your code. Two differences
161
+ from Cloudflare follow from Convex's router:
326
162
 
327
- By default, requests for a path with no file extension that doesn't match an uploaded file fall back
328
- to `index.html`, so client-side routes like `/dashboard/settings` work on reload. For a multi-page
329
- app where unknown paths should be a real 404, deploy with `--no-spa`:
163
+ - An exact route in `convex/http.ts` wins over an asset at the same path.
164
+ - On a static site, a request that is not a navigation and matches no file (`curl /missing`) answers
165
+ 404, not the single-page fallback.
330
166
 
331
- ```bash
332
- npx @samebase/convex-hosting deploy --no-spa
333
- ```
167
+ ## Open tabs after a deploy
334
168
 
335
- The setting is stored with the deployment, so it travels with the code you ship rather than living
336
- in a separate env var. Requests for paths with an extension (e.g. `/missing.js`) always 404 when not
337
- found, regardless of this setting.
338
-
339
- ## Custom routing (SSR, prerendered pages)
340
-
341
- `registerStaticRoutes` decides what every unmatched path returns. When your app needs to decide
342
- instead, for example to server-render pages, serve prerendered pages next to an SPA shell, or
343
- redirect to canonical URLs, write the catch-all route yourself and call `serveStaticAsset` for
344
- uploaded files. Install the component without `httpPrefix`, as in
345
- [Keep existing HTTP routes at the root](#keep-existing-http-routes-at-the-root).
169
+ A page opened before a deploy keeps asking for its hashed files, which now answer 404, as on
170
+ Cloudflare. To reload open tabs on a new version, add a query that returns the bundled version:
346
171
 
347
172
  ```ts
348
- import { httpRouter } from "convex/server";
349
- import { decodeRequestPath, serveStaticAsset } from "@samebase/convex-hosting";
350
- import { components } from "./_generated/api";
351
- import { httpAction } from "./_generated/server";
352
-
353
- const http = httpRouter();
354
-
355
- http.route({
356
- pathPrefix: "/",
357
- method: "GET",
358
- handler: httpAction(async (ctx, request) => {
359
- const file = await serveStaticAsset(ctx, components.hosting, request);
360
- if (file) return file;
361
-
362
- // A missing bundle file is a 404, not a page.
363
- const path = decodeRequestPath(new URL(request.url).pathname);
364
- if (path === null || path.startsWith("/assets/")) {
365
- return new Response("Not Found", {
366
- status: 404,
367
- headers: { "Cache-Control": "no-store" },
368
- });
369
- }
370
- return await renderPage(request);
371
- }),
372
- });
173
+ // convex/siteVersion.ts
174
+ import { v } from "convex/values";
175
+ import { query } from "./_generated/server.js";
176
+ import { versionId } from "../.convex-hosting/version.js";
373
177
 
374
- export default http;
178
+ export const get = query({ args: {}, returns: v.string(), handler: () => versionId });
375
179
  ```
376
180
 
377
- `serveStaticAsset` returns the file with the same headers as `registerStaticRoutes`, or `null` when
378
- no file matches, including a request path with malformed percent-encoding. It never returns the
379
- setup page or a 404, so your code decides what happens next. Only a found file whose storage can't
380
- be read gets an error response (500). The lookup is exact: `/` is not mapped to `/index.html`, and
381
- SPA fallback is off unless you pass `spaFallback: true`.
382
-
383
- Pass `path` to serve a different file, such as a prerendered page or the SPA shell. It is the
384
- decoded path of the uploaded file, starting with `/`. If your route has a prefix, remove it from
385
- `path`:
386
-
387
- ```ts
388
- const page = await serveStaticAsset(ctx, components.hosting, request, {
389
- path: "/about/index.html",
390
- });
391
- const shell = await serveStaticAsset(ctx, components.hosting, request, {
392
- path: "/index.html",
393
- });
394
- ```
395
-
396
- When the backend renders your pages, pushing the backend and uploading files are separate steps, so
397
- right after a deploy a page can briefly refer to scripts that are not uploaded yet. That is why the
398
- missing-asset response above uses `Cache-Control: no-store`: a CDN would otherwise keep serving
399
- the 404.
400
-
401
- Use `decodeRequestPath` for your own path checks, so an encoded request such as `/%61ssets/app.js`
402
- still matches `/assets/`.
403
-
404
- See
405
- [`examples/tanstack-ssr/`](https://github.com/samebase/convex-hosting/tree/main/examples/tanstack-ssr)
406
- for TanStack Start pages rendered in a Convex HTTP action this way.
181
+ and reload when its value changes after the first one:
407
182
 
408
- ## Switching from @convex-dev/static-hosting
409
-
410
- This package continues `@convex-dev/static-hosting` 0.2. To switch an app:
411
-
412
- 1. Replace the dependency with `@samebase/convex-hosting`, and change every import from
413
- `@convex-dev/static-hosting` to `@samebase/convex-hosting`, including `/convex.config` and
414
- `/react`.
415
- 2. Keep the existing component instance, so the uploaded files and the site URLs stay:
416
-
417
- ```ts
418
- import hosting from "@samebase/convex-hosting/convex.config";
419
-
420
- app.use(hosting, { name: "staticHosting", httpPrefix: "/" });
421
- ```
422
-
423
- Keep the `httpPrefix` the app used before. The code keeps using `components.staticHosting`, and
424
- the CLI finds this instance without `--component`. It prints a note that the default name is now
425
- `hosting`.
426
-
427
- 3. Rename `convex/staticHosting.ts` to `convex/hosting.ts`. `<UpdateBanner />` and
428
- `useDeploymentUpdates()` look up `api.hosting.getCurrentDeployment` by default. To keep the old
429
- file, pass `api.staticHosting.getCurrentDeployment` to them instead.
430
- 4. In package scripts, replace `@convex-dev/static-hosting` with `@samebase/convex-hosting` (for
431
- example `npx @samebase/convex-hosting deploy`) and the bare `static-hosting` command with
432
- `convex-hosting`. In the bundler config, replace `STATIC_HOSTING_BASE_PATH` with
433
- `CONVEX_HOSTING_BASE_PATH`.
434
- 5. Deploy as usual.
435
-
436
- To use the new default name instead, remove `name: "staticHosting"`, replace
437
- `components.staticHosting` with `components.hosting`, and deploy. The new instance starts empty, so
438
- the deploy uploads the files again.
439
-
440
- An app on `@convex-dev/static-hosting` 0.1.x first follows upstream's
441
- [0.1.x to 0.2.x migration guide](https://github.com/get-convex/static-hosting/blob/main/MIGRATION.md),
442
- because 0.2 moved the files into the component.
443
-
444
- ## How it works
445
-
446
- 1. **Build:** your bundler emits `dist/`.
447
- 2. **Upload:** the CLI uses your authenticated Convex session to generate signed upload URLs, push
448
- files to Convex storage, record metadata, and GC old deployments.
449
- 3. **Serve:** an HTTP action looks up the requested path, streams the file with the right
450
- `Content-Type`, applies long-term caching for hashed assets, and falls back to `index.html` for
451
- SPA routes.
452
-
453
- ## Example
454
-
455
- See [`examples/spa/`](https://github.com/samebase/convex-hosting/tree/main/examples/spa) for a
456
- complete Vite + React app, and
457
- [`examples/tanstack-ssr/`](https://github.com/samebase/convex-hosting/tree/main/examples/tanstack-ssr)
458
- for server rendering with TanStack Start.
183
+ ```tsx
184
+ import { useQuery } from "convex/react";
185
+ import { useEffect, useRef } from "react";
186
+ import { api } from "../convex/_generated/api";
459
187
 
460
- ```bash
461
- pnpm install
462
- cd examples/spa
463
- pnpm run dev
188
+ export function useReloadOnNewVersion() {
189
+ const versionId = useQuery(api.siteVersion.get);
190
+ const first = useRef<string | undefined>(undefined);
191
+ useEffect(() => {
192
+ if (versionId === undefined) return;
193
+ first.current ??= versionId;
194
+ if (versionId !== first.current) location.reload();
195
+ }, [versionId]);
196
+ }
464
197
  ```
465
198
 
466
- ## Contributing
199
+ ## Server-rendered pages
467
200
 
468
- See [CONTRIBUTING.md](https://github.com/samebase/convex-hosting/blob/main/CONTRIBUTING.md).
201
+ [`@samebase/convex-hosting-tanstack-start`](https://github.com/samebase/convex-hosting/tree/main/packages/convex-hosting-tanstack-start)
202
+ renders TanStack Start pages in the same route, after `serveAsset`. See the
203
+ [TanStack Start example](https://github.com/samebase/convex-hosting/tree/main/examples/tanstack-ssr).
469
204
 
470
205
  ## License
471
206
 
472
- Apache-2.0
207
+ Apache-2.0, with the notices in
208
+ [NOTICE](https://github.com/samebase/convex-hosting/blob/main/NOTICE). Not affiliated with or
209
+ endorsed by Convex. To work on this repository, see
210
+ [CONTRIBUTING.md](https://github.com/samebase/convex-hosting/blob/main/CONTRIBUTING.md).