@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.
- package/CHANGELOG.md +17 -132
- package/README.md +188 -406
- 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 +377 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/client/index.d.ts +8 -115
- package/dist/client/index.js +65 -242
- 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 +21 -37
- 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 -113
- 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 -23
- package/dist/component/serving.d.ts.map +0 -1
- package/dist/component/serving.js +0 -161
- 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 -549
- package/src/client/index.ts +0 -344
- 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 -254
- package/src/component/http.ts +0 -133
- 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 -170
- 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,472 +1,254 @@
|
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
16
|
+
1. Install:
|
|
50
17
|
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
22
|
+
2. `convex/convex.config.ts` mounts the component:
|
|
83
23
|
|
|
84
|
-
|
|
85
|
-
|
|
24
|
+
```ts
|
|
25
|
+
import { defineApp } from "convex/server";
|
|
26
|
+
import hosting from "@samebase/convex-hosting/convex.config";
|
|
86
27
|
|
|
87
|
-
|
|
28
|
+
const app = defineApp();
|
|
29
|
+
app.use(hosting);
|
|
88
30
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
import hosting from "@samebase/convex-hosting/convex.config";
|
|
31
|
+
export default app;
|
|
32
|
+
```
|
|
92
33
|
|
|
93
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
76
|
+
5. Deploy, from CI or a terminal:
|
|
107
77
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
78
|
+
```sh
|
|
79
|
+
npx convex-hosting deploy --build "vite build" --dist dist --not-found-handling single-page-application
|
|
80
|
+
```
|
|
111
81
|
|
|
112
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
95
|
+
## Deploy
|
|
120
96
|
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
113
|
+
## Deploy tools and CI
|
|
135
114
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
131
|
+
When the development deployment should serve the current build, run the one-shot command:
|
|
151
132
|
|
|
152
|
-
```
|
|
153
|
-
npx convex
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
147
|
+
Two cases are transient:
|
|
165
148
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
156
|
+
## Options
|
|
172
157
|
|
|
173
|
-
|
|
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
|
-
|
|
166
|
+
The two handling options work as
|
|
167
|
+
[Cloudflare documents them](https://developers.cloudflare.com/workers/static-assets/routing/).
|
|
176
168
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
182
|
-
npm run dev
|
|
183
|
-
```
|
|
173
|
+
## `_headers`, `_redirects`, and `.assetsignore`
|
|
184
174
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
228
|
-
production `CONVEX_DEPLOY_KEY` takes precedence over it.
|
|
192
|
+
## Routing
|
|
229
193
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
237
|
-
|
|
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
|
-
|
|
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
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
##
|
|
211
|
+
## Open tabs after a deploy
|
|
248
212
|
|
|
249
|
-
|
|
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
|
-
|
|
266
|
-
import {
|
|
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 {
|
|
222
|
+
export const get = query({ args: {}, returns: v.string(), handler: () => versionId });
|
|
269
223
|
```
|
|
270
224
|
|
|
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:
|
|
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
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
-
##
|
|
243
|
+
## Server-rendered pages
|
|
467
244
|
|
|
468
|
-
|
|
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).
|