@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.
- package/CHANGELOG.md +10 -128
- package/README.md +136 -398
- package/dist/cli/convex.d.ts +63 -0
- package/dist/cli/convex.js +108 -0
- package/dist/cli/convex.js.map +1 -0
- package/dist/cli/files.d.ts +18 -0
- package/dist/cli/files.js +106 -0
- package/dist/cli/files.js.map +1 -0
- package/dist/cli/index.d.ts +1 -11
- package/dist/cli/index.js +3 -58
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/main.d.ts +5 -0
- package/dist/cli/main.js +346 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/client/index.d.ts +8 -115
- package/dist/client/index.js +65 -254
- package/dist/client/index.js.map +1 -1
- package/dist/client/types.d.ts +5 -0
- package/dist/client/version-module.d.ts +6 -0
- package/dist/component/_generated/api.d.ts +0 -5
- package/dist/component/_generated/api.js.map +1 -1
- package/dist/component/_generated/component.d.ts +20 -18
- package/dist/component/_generated/dataModel.d.ts +0 -1
- package/dist/component/_generated/server.d.ts +12 -1
- package/dist/component/_generated/server.js +2 -0
- package/dist/component/_generated/server.js.map +1 -1
- package/dist/component/convex.config.d.ts +0 -1
- package/dist/component/lib.d.ts +175 -156
- package/dist/component/lib.js +396 -710
- package/dist/component/lib.js.map +1 -1
- package/dist/component/schema.d.ts +76 -72
- package/dist/component/schema.js +35 -59
- package/dist/component/schema.js.map +1 -1
- package/dist/workers-shared/asset-worker/src/analytics.d.ts +39 -0
- package/dist/workers-shared/asset-worker/src/analytics.js +86 -0
- package/dist/workers-shared/asset-worker/src/analytics.js.map +1 -0
- package/dist/workers-shared/asset-worker/src/compatibility-flags.d.ts +19 -0
- package/dist/workers-shared/asset-worker/src/compatibility-flags.js +29 -0
- package/dist/workers-shared/asset-worker/src/compatibility-flags.js.map +1 -0
- package/dist/workers-shared/asset-worker/src/configuration.d.ts +3 -0
- package/dist/workers-shared/asset-worker/src/configuration.js +30 -0
- package/dist/workers-shared/asset-worker/src/configuration.js.map +1 -0
- package/dist/workers-shared/asset-worker/src/constants.d.ts +1 -0
- package/dist/workers-shared/asset-worker/src/constants.js +3 -0
- package/dist/workers-shared/asset-worker/src/constants.js.map +1 -0
- package/dist/workers-shared/asset-worker/src/handler.d.ts +28 -0
- package/dist/workers-shared/asset-worker/src/handler.js +717 -0
- package/dist/workers-shared/asset-worker/src/handler.js.map +1 -0
- package/dist/workers-shared/asset-worker/src/types.d.ts +13 -0
- package/dist/workers-shared/asset-worker/src/types.js +2 -0
- package/dist/workers-shared/asset-worker/src/types.js.map +1 -0
- package/dist/workers-shared/asset-worker/src/utils/base-path.d.ts +29 -0
- package/dist/workers-shared/asset-worker/src/utils/base-path.js +56 -0
- package/dist/workers-shared/asset-worker/src/utils/base-path.js.map +1 -0
- package/dist/workers-shared/asset-worker/src/utils/headers.d.ts +11 -0
- package/dist/workers-shared/asset-worker/src/utils/headers.js +77 -0
- package/dist/workers-shared/asset-worker/src/utils/headers.js.map +1 -0
- package/dist/workers-shared/asset-worker/src/utils/rules-engine.d.ts +23 -0
- package/dist/workers-shared/asset-worker/src/utils/rules-engine.js +133 -0
- package/dist/workers-shared/asset-worker/src/utils/rules-engine.js.map +1 -0
- package/dist/workers-shared/asset-worker/src/worker.d.ts +14 -0
- package/dist/workers-shared/asset-worker/src/worker.js +12 -0
- package/dist/workers-shared/asset-worker/src/worker.js.map +1 -0
- package/dist/workers-shared/utils/base-path.d.ts +32 -0
- package/dist/workers-shared/utils/base-path.js +94 -0
- package/dist/workers-shared/utils/base-path.js.map +1 -0
- package/dist/workers-shared/utils/configuration/constants.d.ts +15 -0
- package/dist/workers-shared/utils/configuration/constants.js +16 -0
- package/dist/workers-shared/utils/configuration/constants.js.map +1 -0
- package/dist/workers-shared/utils/configuration/constructConfiguration.d.ts +12 -0
- package/dist/workers-shared/utils/configuration/constructConfiguration.js +103 -0
- package/dist/workers-shared/utils/configuration/constructConfiguration.js.map +1 -0
- package/dist/workers-shared/utils/configuration/parseHeaders.d.ts +5 -0
- package/dist/workers-shared/utils/configuration/parseHeaders.js +181 -0
- package/dist/workers-shared/utils/configuration/parseHeaders.js.map +1 -0
- package/dist/workers-shared/utils/configuration/parseRedirects.d.ts +8 -0
- package/dist/workers-shared/utils/configuration/parseRedirects.js +129 -0
- package/dist/workers-shared/utils/configuration/parseRedirects.js.map +1 -0
- package/dist/workers-shared/utils/configuration/parseStaticRouting.d.ts +2 -0
- package/dist/workers-shared/utils/configuration/parseStaticRouting.js +94 -0
- package/dist/workers-shared/utils/configuration/parseStaticRouting.js.map +1 -0
- package/dist/workers-shared/utils/configuration/types.d.ts +38 -0
- package/dist/workers-shared/utils/configuration/types.js +2 -0
- package/dist/workers-shared/utils/configuration/types.js.map +1 -0
- package/dist/workers-shared/utils/configuration/validateURL.d.ts +3 -0
- package/dist/workers-shared/utils/configuration/validateURL.js +56 -0
- package/dist/workers-shared/utils/configuration/validateURL.js.map +1 -0
- package/dist/workers-shared/utils/constants.d.ts +28 -0
- package/dist/workers-shared/utils/constants.js +34 -0
- package/dist/workers-shared/utils/constants.js.map +1 -0
- package/dist/workers-shared/utils/helpers.d.ts +22 -0
- package/dist/workers-shared/utils/helpers.js +73 -0
- package/dist/workers-shared/utils/helpers.js.map +1 -0
- package/dist/workers-shared/utils/responses.d.ts +43 -0
- package/dist/workers-shared/utils/responses.js +125 -0
- package/dist/workers-shared/utils/responses.js.map +1 -0
- package/dist/workers-shared/utils/tracing.d.ts +3 -0
- package/dist/workers-shared/utils/tracing.js +29 -0
- package/dist/workers-shared/utils/tracing.js.map +1 -0
- package/dist/workers-shared/utils/types.d.ts +131 -0
- package/dist/workers-shared/utils/types.js +72 -0
- package/dist/workers-shared/utils/types.js.map +1 -0
- package/package.json +16 -32
- package/INTEGRATION.md +0 -399
- package/dist/cli/args.d.ts +0 -28
- package/dist/cli/args.d.ts.map +0 -1
- package/dist/cli/args.js +0 -148
- package/dist/cli/args.js.map +0 -1
- package/dist/cli/argumentChunks.d.ts +0 -3
- package/dist/cli/argumentChunks.d.ts.map +0 -1
- package/dist/cli/argumentChunks.js +0 -29
- package/dist/cli/argumentChunks.js.map +0 -1
- package/dist/cli/collectFiles.d.ts +0 -10
- package/dist/cli/collectFiles.d.ts.map +0 -1
- package/dist/cli/collectFiles.js +0 -53
- package/dist/cli/collectFiles.js.map +0 -1
- package/dist/cli/commands.d.ts +0 -8
- package/dist/cli/commands.d.ts.map +0 -1
- package/dist/cli/commands.js +0 -56
- package/dist/cli/commands.js.map +0 -1
- package/dist/cli/componentName.d.ts +0 -32
- package/dist/cli/componentName.d.ts.map +0 -1
- package/dist/cli/componentName.js +0 -43
- package/dist/cli/componentName.js.map +0 -1
- package/dist/cli/deploy.d.ts +0 -16
- package/dist/cli/deploy.d.ts.map +0 -1
- package/dist/cli/deploy.js +0 -283
- package/dist/cli/deploy.js.map +0 -1
- package/dist/cli/deployEnvironment.d.ts +0 -10
- package/dist/cli/deployEnvironment.d.ts.map +0 -1
- package/dist/cli/deployEnvironment.js +0 -12
- package/dist/cli/deployEnvironment.js.map +0 -1
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/upload.d.ts +0 -22
- package/dist/cli/upload.d.ts.map +0 -1
- package/dist/cli/upload.js +0 -630
- package/dist/cli/upload.js.map +0 -1
- package/dist/client/_generated/_ignore.d.ts +0 -1
- package/dist/client/_generated/_ignore.d.ts.map +0 -1
- package/dist/client/_generated/_ignore.js +0 -3
- package/dist/client/_generated/_ignore.js.map +0 -1
- package/dist/client/index.d.ts.map +0 -1
- package/dist/component/_generated/api.d.ts.map +0 -1
- package/dist/component/_generated/component.d.ts.map +0 -1
- package/dist/component/_generated/dataModel.d.ts.map +0 -1
- package/dist/component/_generated/server.d.ts.map +0 -1
- package/dist/component/convex.config.d.ts.map +0 -1
- package/dist/component/http.d.ts +0 -4
- package/dist/component/http.d.ts.map +0 -1
- package/dist/component/http.js +0 -125
- package/dist/component/http.js.map +0 -1
- package/dist/component/lib.d.ts.map +0 -1
- package/dist/component/schema.d.ts.map +0 -1
- package/dist/component/serving.d.ts +0 -17
- package/dist/component/serving.d.ts.map +0 -1
- package/dist/component/serving.js +0 -150
- package/dist/component/serving.js.map +0 -1
- package/dist/react/index.d.ts +0 -59
- package/dist/react/index.d.ts.map +0 -1
- package/dist/react/index.js +0 -137
- package/dist/react/index.js.map +0 -1
- package/src/cli/args.test.ts +0 -79
- package/src/cli/args.ts +0 -153
- package/src/cli/argumentChunks.test.ts +0 -27
- package/src/cli/argumentChunks.ts +0 -40
- package/src/cli/collectFiles.test.ts +0 -26
- package/src/cli/collectFiles.ts +0 -59
- package/src/cli/commands.ts +0 -68
- package/src/cli/componentName.test.ts +0 -41
- package/src/cli/componentName.ts +0 -46
- package/src/cli/deploy.ts +0 -336
- package/src/cli/deployEnvironment.test.ts +0 -27
- package/src/cli/deployEnvironment.ts +0 -17
- package/src/cli/index.ts +0 -65
- package/src/cli/upload.ts +0 -837
- package/src/client/_generated/_ignore.ts +0 -1
- package/src/client/index.test.ts +0 -510
- package/src/client/index.ts +0 -355
- package/src/client/setup.test.ts +0 -22
- package/src/component/_generated/api.ts +0 -54
- package/src/component/_generated/component.ts +0 -55
- package/src/component/_generated/dataModel.ts +0 -60
- package/src/component/_generated/server.ts +0 -156
- package/src/component/convex.config.ts +0 -3
- package/src/component/http.test.ts +0 -223
- package/src/component/http.ts +0 -144
- package/src/component/lib.test.ts +0 -1007
- package/src/component/lib.ts +0 -862
- package/src/component/schema.ts +0 -75
- package/src/component/serving.ts +0 -158
- package/src/component/setup.test.ts +0 -11
- package/src/react/index.tsx +0 -197
- package/src/test.ts +0 -23
package/README.md
CHANGED
|
@@ -1,89 +1,20 @@
|
|
|
1
1
|
# Convex Hosting
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
`
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
12
|
-
(Apache-2.0). It is not affiliated with or endorsed by Convex.
|
|
12
|
+
## Install
|
|
13
13
|
|
|
14
|
-
|
|
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);
|
|
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 {
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
61
|
+
## Deploy
|
|
151
62
|
|
|
152
|
-
```
|
|
153
|
-
npx convex
|
|
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
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
178
|
-
# Terminal 1
|
|
179
|
-
npx convex dev
|
|
84
|
+
Two deploys that overlap both complete, and the last push wins.
|
|
180
85
|
|
|
181
|
-
|
|
182
|
-
npm run dev
|
|
183
|
-
```
|
|
86
|
+
## Development
|
|
184
87
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
92
|
+
When the development deployment should serve the current build, run the one-shot command:
|
|
191
93
|
|
|
192
|
-
```
|
|
193
|
-
npx
|
|
94
|
+
```sh
|
|
95
|
+
npx convex-hosting deploy --dev --build "vite build" --dist dist --not-found-handling single-page-application
|
|
194
96
|
```
|
|
195
97
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
228
|
-
production `CONVEX_DEPLOY_KEY` takes precedence over it.
|
|
109
|
+
Two cases are transient:
|
|
229
110
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
237
|
-
GET request rather than a HEAD request.
|
|
118
|
+
## Options
|
|
238
119
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
`
|
|
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
|
-
|
|
244
|
-
|
|
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
|
-
|
|
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
|
-
|
|
135
|
+
## `_headers`, `_redirects`, and `.assetsignore`
|
|
250
136
|
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
255
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
323
|
-
|
|
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
|
-
|
|
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
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
332
|
-
npx @samebase/convex-hosting deploy --no-spa
|
|
333
|
-
```
|
|
167
|
+
## Open tabs after a deploy
|
|
334
168
|
|
|
335
|
-
|
|
336
|
-
|
|
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
|
-
|
|
349
|
-
import {
|
|
350
|
-
import {
|
|
351
|
-
import {
|
|
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
|
|
178
|
+
export const get = query({ args: {}, returns: v.string(), handler: () => versionId });
|
|
375
179
|
```
|
|
376
180
|
|
|
377
|
-
|
|
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
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
-
##
|
|
199
|
+
## Server-rendered pages
|
|
467
200
|
|
|
468
|
-
|
|
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).
|