@samebase/convex-hosting 0.3.2 → 0.4.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 (193) hide show
  1. package/CHANGELOG.md +17 -132
  2. package/README.md +188 -406
  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 +377 -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 -242
  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 +21 -37
  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 -113
  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 -23
  155. package/dist/component/serving.d.ts.map +0 -1
  156. package/dist/component/serving.js +0 -161
  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 -549
  178. package/src/client/index.ts +0 -344
  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 -254
  186. package/src/component/http.ts +0 -133
  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 -170
  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,472 +1,254 @@
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).
10
-
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.
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`.
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.
38
11
 
39
12
  ## Setup
40
13
 
41
- ### 1. Install
42
-
43
- ```bash
44
- npm install @samebase/convex-hosting
45
- ```
46
-
47
- ### 2. Register the component
14
+ Requires `convex` 1.46 or later.
48
15
 
49
- `convex/convex.config.ts`:
16
+ 1. Install:
50
17
 
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.
18
+ ```sh
19
+ npm install @samebase/convex-hosting
20
+ ```
81
21
 
82
- ### Keep existing HTTP routes at the root
22
+ 2. `convex/convex.config.ts` mounts the component:
83
23
 
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.
24
+ ```ts
25
+ import { defineApp } from "convex/server";
26
+ import hosting from "@samebase/convex-hosting/convex.config";
86
27
 
87
- `convex/convex.config.ts`:
28
+ const app = defineApp();
29
+ app.use(hosting);
88
30
 
89
- ```ts
90
- import { defineApp } from "convex/server";
91
- import hosting from "@samebase/convex-hosting/convex.config";
31
+ export default app;
32
+ ```
92
33
 
93
- const app = defineApp();
94
- app.use(hosting); // no httpPrefix
34
+ 3. `convex/http.ts` has one `GET` route for the site. Convex answers `HEAD` through it.
95
35
 
96
- export default app;
97
- ```
36
+ ```ts
37
+ import { httpRouter } from "convex/server";
38
+ import { serveAsset } from "@samebase/convex-hosting";
39
+ import { components } from "./_generated/api.js";
40
+ import { httpAction } from "./_generated/server.js";
41
+ import { versionId } from "../.convex-hosting/version.js";
42
+
43
+ const http = httpRouter();
44
+
45
+ http.route({
46
+ pathPrefix: "/",
47
+ method: "GET",
48
+ handler: httpAction(async (ctx, request) => {
49
+ const asset = await serveAsset(ctx, components.hosting, request, versionId);
50
+ if (asset) return asset;
51
+ // A static site: 404. A server-rendered site: render the page here, also with `no-store`.
52
+ // The deployment's Cloudflare front caches answers on static-looking paths.
53
+ return new Response(null, { status: 404, headers: { "Cache-Control": "no-store" } });
54
+ }),
55
+ });
56
+
57
+ export default http;
58
+ ```
98
59
 
99
- `convex/http.ts`:
60
+ 4. Run `convex-hosting init` before `npx convex dev`, once per clone. `convex/http.ts` imports
61
+ `.convex-hosting/version.js`, which only a deploy writes, so without it `npx convex dev` fails
62
+ with `Could not resolve "../.convex-hosting/version.js"`. `init` writes the file with no site
63
+ version and keeps an existing one. Put it in the script that starts `npx convex dev`:
64
+
65
+ ```json
66
+ {
67
+ "scripts": {
68
+ "predev": "convex-hosting init",
69
+ "dev": "convex dev"
70
+ }
71
+ }
72
+ ```
100
73
 
101
- ```ts
102
- import { httpRouter } from "convex/server";
103
- import { registerStaticRoutes } from "@samebase/convex-hosting";
104
- import { components } from "./_generated/api";
74
+ Until the first deploy, the site answers 404.
105
75
 
106
- const http = httpRouter();
76
+ 5. Deploy, from CI or a terminal:
107
77
 
108
- // Register exact app routes first. Existing auth/webhook helpers can stay here.
109
- // auth.addHttpRoutes(http);
110
- registerStaticRoutes(http, components.hosting);
78
+ ```sh
79
+ npx convex-hosting deploy --build "vite build" --dist dist --not-found-handling single-page-application
80
+ ```
111
81
 
112
- export default http;
113
- ```
82
+ `convex-hosting` keeps its state in `.convex-hosting/` at the project root, next to `convex/`, like
83
+ Wrangler's `.wrangler/`. The folder has its own `.gitignore`, so git ignores it. The package
84
+ declares the type of `.convex-hosting/version.js`, so the app typechecks without it.
85
+
86
+ ## Rules
114
87
 
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.
88
+ - Deploy production and previews with `convex-hosting deploy`, never with a plain
89
+ `npx convex deploy`. A plain push sends whatever version `.convex-hosting/version.js` names, and
90
+ after `init` that is none: the site answers 404 until the next `convex-hosting deploy`.
91
+ - Do not commit `.convex-hosting/`. It ignores itself.
92
+ - Do not change directory in `--build`. The build and the upload run in the project directory.
93
+ - Answer a miss in `convex/http.ts` with `Cache-Control: no-store`, as in step 3.
118
94
 
119
- ### Using non-Vite bundlers
95
+ ## Deploy
120
96
 
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:
97
+ `deploy` runs `npx convex deploy` with your build and the upload of `--dist` as its `--cmd`. The
98
+ pushed code names the version it was built with, so the push switches the site, and the command
99
+ ends. The first request that the new code serves marks its version live, which deletes the versions
100
+ that were live before it. A version that never becomes live, from a deploy that failed, is deleted
101
+ when it is a day old, with the stored files that no version uses. Nothing is deleted while a site
102
+ gets no request.
123
103
 
124
- **For Expo:**
104
+ `deploy` goes to production. `--preview-name <name>` sends it to that preview (with a login or a
105
+ project's preview deploy key; always pass the name with that key), and `--dev` to the development
106
+ deployment (see [Development](#development)). A deploy key in `CONVEX_DEPLOY_KEY` or
107
+ `CONVEX_DEPLOYMENT_TOKEN` (from the environment or `.env.local`) selects its own deployment instead,
108
+ and so does `CONVEX_SELF_HOSTED_URL` for a self-hosted deployment.
125
109
 
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
- ```
110
+ The first deploy to a deployment pushes the component, then uploads the files. Until that upload
111
+ ends, the site answers 404. Two deploys that overlap both complete, and the last push wins.
133
112
 
134
- **For Next.js:**
113
+ ## Deploy tools and CI
135
114
 
136
- ```json
137
- {
138
- "scripts": {
139
- "build": "NEXT_PUBLIC_CONVEX_URL=${VITE_CONVEX_URL:-$NEXT_PUBLIC_CONVEX_URL} next build"
140
- }
141
- }
142
- ```
115
+ - **A tool that runs `convex deploy` itself**, such as Alchemy's `Convex.Code`, has no place for the
116
+ upload, which must happen between the build and the push. Run `convex-hosting deploy` in place of
117
+ that tool's push step, with the deploy key in `CONVEX_DEPLOY_KEY`.
118
+ - **A deploy key limited to some actions** needs `deployment:deploy`,
119
+ `deployment:functions:runInternalQueries`, `deployment:functions:runInternalMutations`,
120
+ `deployment:functions:runInternalActions` (the upload calls one internal action), and
121
+ `deployment:functions:runTestQuery` (the first-install check is an inline query).
122
+ - **Checks that must stop a release** run before `convex-hosting deploy`, because its push switches
123
+ the site. Checks that need the new functions run after it.
143
124
 
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.
125
+ ## Development
147
126
 
148
- ## Deployment
127
+ The inner loop is your framework's dev server, such as Vite's, which serves the frontend locally
128
+ with hot reload against the development deployment's URL from `.env.local`, plus `npx convex dev`,
129
+ which pushes your function changes. Neither needs a deploy.
149
130
 
150
- Deploy both Convex backend and static files with a single command:
131
+ When the development deployment should serve the current build, run the one-shot command:
151
132
 
152
- ```bash
153
- npx convex login # first time only
154
- npx @samebase/convex-hosting deploy
133
+ ```sh
134
+ npx convex-hosting deploy --dev --build "vite build" --dist dist --not-found-handling single-page-application
155
135
  ```
156
136
 
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.
137
+ `deploy --dev` goes to the development deployment of this checkout, the one in `.env.local`. It runs
138
+ the build itself, uploads `--dist`, writes `.convex-hosting/version.js`, and pushes with
139
+ `npx convex run --push`, which works whether or not `npx convex dev` runs. The build runs with your
140
+ environment, so the framework reads the deployment's URL from `.env.local` (Vite reads
141
+ `VITE_CONVEX_URL`). Against an anonymous local deployment, `deploy --dev` needs `npx convex dev`
142
+ running, because without it every Convex CLI call starts and stops its own backend and the upload's
143
+ requests find nothing; cloud development deployments do not have this constraint. There is no watch
144
+ mode: a build, an upload, and a push per change is not an inner loop, and Cloudflare's own tooling
145
+ works the same way, with a local dev server and a push on demand.
163
146
 
164
- For more control, you can run the two halves separately:
147
+ Two cases are transient:
165
148
 
166
- ```bash
167
- npx convex deploy
168
- npx @samebase/convex-hosting upload --build --prod
169
- ```
149
+ - A server-rendered app imports `dist/server` in `convex/http.ts`, so a running `npx convex dev`
150
+ pushes after the build, before `deploy --dev` writes the new version. Until `deploy --dev` ends,
151
+ the new pages refer to files that answer 404.
152
+ - A production deploy writes production's version to the version file, and a running
153
+ `npx convex dev` pushes it to the development deployment, where that version does not exist, until
154
+ the next `deploy --dev`. Run production deploys in CI or in a separate checkout.
170
155
 
171
- Your app is live at `https://<deployment>.convex.site`.
156
+ ## Options
172
157
 
173
- ## Development workflow
158
+ | Option | Values |
159
+ | ------------------------------ | -------------------------------------------------------------------------------------- |
160
+ | `--dev` | The development deployment of this checkout, instead of production |
161
+ | `--preview-name <name>` | The preview deployment with this name, instead of production |
162
+ | `--html-handling <value>` | `auto-trailing-slash` (default), `force-trailing-slash`, `drop-trailing-slash`, `none` |
163
+ | `--not-found-handling <value>` | `none` (default), `single-page-application`, `404-page` |
164
+ | `--component <name>` | The component instance, `hosting` by default |
174
165
 
175
- Use your normal frontend dev server during development:
166
+ The two handling options work as
167
+ [Cloudflare documents them](https://developers.cloudflare.com/workers/static-assets/routing/).
176
168
 
177
- ```bash
178
- # Terminal 1
179
- npx convex dev
169
+ Each file of `--dist` must be 25 MiB or smaller, and the CLI names a file that is larger. This is
170
+ Cloudflare's limit per asset, and the HTTP action that serves a file cannot answer more than
171
+ Convex's 100 MiB response limit anyway.
180
172
 
181
- # Terminal 2
182
- npm run dev
183
- ```
173
+ ## `_headers`, `_redirects`, and `.assetsignore`
184
174
 
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.
175
+ Put them at the root of `--dist`, as for Cloudflare:
176
+ [`_headers`](https://developers.cloudflare.com/workers/static-assets/headers/),
177
+ [`_redirects`](https://developers.cloudflare.com/workers/static-assets/redirects/), and
178
+ [`.assetsignore`](https://developers.cloudflare.com/workers/static-assets/binding/#ignoring-assets).
179
+ For a Vite app, put them in `public/`, which Vite copies into `dist/`. They are not served. Symbolic
180
+ links in `--dist` are followed, as Wrangler follows them, also when they point outside the folder; a
181
+ link to nothing, or back into a folder already included, is skipped with a message. `.assetsignore`
182
+ excludes what a link should not publish.
189
183
 
190
- Before release, run one hosted smoke test against the development deployment:
184
+ Every asset has `Cache-Control: public, max-age=0, must-revalidate` and an `ETag`. To cache hashed
185
+ files for a year, add a `_headers` rule:
191
186
 
192
- ```bash
193
- npx @samebase/convex-hosting upload --build
194
187
  ```
195
-
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)
188
+ /assets/*
189
+ Cache-Control: public, max-age=31536000, immutable
225
190
  ```
226
191
 
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.
192
+ ## Routing
229
193
 
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.
194
+ `serveAsset` returns the asset's response, or `null` when no asset matches, so your route continues.
195
+ When the version does not exist or is still uploading, it returns a 404 response that names the
196
+ version. At the first request for a new version, it marks the version live.
235
197
 
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.
198
+ `not_found_handling` applies to navigation requests (`Sec-Fetch-Mode: navigate`) only, as on
199
+ Cloudflare when a Worker runs: other requests that match no file reach your code. Browsers send that
200
+ header whenever they open a page. `curl`, `fetch` in scripts and tests, uptime monitors, and
201
+ link-preview bots do not, so they get your route's answer for a deep link of a single-page app,
202
+ a 404. To test a deep link as a browser opens it, send the header:
203
+ `curl -H "Sec-Fetch-Mode: navigate"`, or `node:http` in Node, whose `fetch` refuses to set it.
238
204
 
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`.
205
+ Two differences from Cloudflare follow from Convex's router:
242
206
 
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.
207
+ - An exact route in `convex/http.ts` wins over an asset at the same path.
208
+ - On a static site, a request that is not a navigation and matches no file (`curl /missing`) answers
209
+ 404, not the single-page fallback.
246
210
 
247
- ## Security
211
+ ## Open tabs after a deploy
248
212
 
249
- The upload API uses **internal functions** in the Component that can only be called via:
250
-
251
- - `npx convex run` (requires Convex CLI authentication)
252
- - Other Convex functions in the Component (server-side only)
253
-
254
- This means unauthorized users **cannot** upload files to your site, even if they know your Convex
255
- URL.
256
-
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`:
213
+ A page opened before a deploy keeps asking for its hashed files, which now answer 404, as on
214
+ Cloudflare. To reload open tabs on a new version, add a query that returns the bundled version:
263
215
 
264
216
  ```ts
265
- import { exposeDeploymentQuery } from "@samebase/convex-hosting";
266
- import { components } from "./_generated/api";
217
+ // convex/siteVersion.ts
218
+ import { v } from "convex/values";
219
+ import { query } from "./_generated/server.js";
220
+ import { versionId } from "../.convex-hosting/version.js";
267
221
 
268
- export const { getCurrentDeployment } = exposeDeploymentQuery(components.hosting);
222
+ export const get = query({ args: {}, returns: v.string(), handler: () => versionId });
269
223
  ```
270
224
 
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:
225
+ and reload when its value changes after the first one:
288
226
 
289
227
  ```tsx
228
+ import { useQuery } from "convex/react";
229
+ import { useEffect, useRef } from "react";
290
230
  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/" });
308
- ```
309
-
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
- ```
321
-
322
- Root-mounted apps don't need this; the default is `/`. For webpack use `publicPath`, for Next.js
323
- `assetPrefix`.
324
-
325
- ## SPA routing
326
-
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`:
330
-
331
- ```bash
332
- npx @samebase/convex-hosting deploy --no-spa
333
- ```
334
-
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).
346
-
347
- ```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
- });
373
-
374
- export default http;
375
- ```
376
-
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
231
 
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.
407
-
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.
459
-
460
- ```bash
461
- pnpm install
462
- cd examples/spa
463
- pnpm run dev
232
+ export function useReloadOnNewVersion() {
233
+ const versionId = useQuery(api.siteVersion.get);
234
+ const first = useRef<string | undefined>(undefined);
235
+ useEffect(() => {
236
+ if (versionId === undefined) return;
237
+ first.current ??= versionId;
238
+ if (versionId !== first.current) location.reload();
239
+ }, [versionId]);
240
+ }
464
241
  ```
465
242
 
466
- ## Contributing
243
+ ## Server-rendered pages
467
244
 
468
- See [CONTRIBUTING.md](https://github.com/samebase/convex-hosting/blob/main/CONTRIBUTING.md).
245
+ [`@samebase/convex-hosting-tanstack-start`](https://github.com/samebase/convex-hosting/tree/main/packages/convex-hosting-tanstack-start)
246
+ renders TanStack Start pages in the same route, after `serveAsset`. See the
247
+ [TanStack Start example](https://github.com/samebase/convex-hosting/tree/main/examples/tanstack-ssr).
469
248
 
470
249
  ## License
471
250
 
472
- Apache-2.0
251
+ Apache-2.0, with the notices in
252
+ [NOTICE](https://github.com/samebase/convex-hosting/blob/main/NOTICE). Not affiliated with or
253
+ endorsed by Convex. To work on this repository, see
254
+ [CONTRIBUTING.md](https://github.com/samebase/convex-hosting/blob/main/CONTRIBUTING.md).