Mike Cann's avatar
Mike Cann
4 months ago

Share your slop with the Static Hosting Component!

I don't know about you guys, but every time I reach that stage of a project where I've got to take my work from my local computer to show other people in the world, I get this sinking feeling, because I know what's coming.

I'm inevitably going to have to log into some third party dashboard like Vercel, Netlify or Cloudflare, navigate around to find the place where I can add my new project, and go through all the headaches and multiple builds before I realize I'm missing some environment variable or some other build step. It's super manual, super frustrating and I just hate it. But I do need to do it if I want others to see my lovely app that I built, right?

Padme from Star Wars: "Right? ... Right?"Padme from Star Wars: "Right? ... Right?"

Well, maybe not, thanks to the awesome Static Hosting Component. With it we can host our frontend on Convex itself and then show the world. Here's my cat sharing app live on a convex.site URL (see the address bar), with a new cat going in:

0:00 / 0:00

Now you're not going to be running Amazon.com on it or anything like that, but it is perfect for just getting your idea (or your slop 😉) quickly into the hands of others. I've been using it for a while now and I've got a few tips and tricks that I've learned along the way.

One thing before we start. I recorded the video on version 0.1.4, and since then 0.2 has made the setup simpler, so a few things on screen in the video are different now. What's written here is how it works today, on 0.2.1.

Let's take a look.

Setting it up

So we're going to use this lovely cat sharing app as an example. Me and my best buddy Codex vibed this out, we're happy with where it is locally, and it's pointing at our Convex dev deployment. But now we want to get it hosted somewhere so we can share this masterpiece with the world, so let's tell the agent:

please install and use the convex static hosting component so I can share this lovely awesomeness with the rest of the world

Now do make sure you use the words "convex static hosting component", because I've seen the agent go off and get pretty confused if you just say "convex hosting" or "hosting component" or something like that. Alternatively you can just link it directly to the component's docs. The docs by the way are excellent, and they even have this nice little INTEGRATION.md for agents to consume.

After a minute or two it should be done. If you'd rather do it yourself it's only a couple of commands:

1npm install @convex-dev/static-hosting
2npx @convex-dev/static-hosting setup
3

That adds the component to your convex/convex.config.ts:

1// convex/convex.config.ts
2import { defineApp } from "convex/server";
3import staticHosting from "@convex-dev/static-hosting/convex.config";
4
5// Your own HTTP endpoints (convex/http.ts) are served under /api so the
6// static site can own the root.
7const app = defineApp({ httpPrefix: "/api" });
8app.use(staticHosting, { httpPrefix: "/" });
9
10export default app;
11

And a handy script to your package.json:

1{
2  "scripts": {
3    "deploy": "npx @convex-dev/static-hosting deploy"
4  }
5}
6

That's pretty much it. Run npx convex dev so it picks up the component, then npm run deploy, and hopefully if all goes well you've now got a working URL to share at https://<deployment>.convex.site. Bam, it was that easy!

Dev or prod?

One thing to note in that first clip is that those are the same cats I had while I was working on the app locally. That's because the site is pointing at my dev deployment, which you can see in the address bar is sincere-viper-855. This might be what you want, you might want the world to see your development database, but more likely you want a separate production environment so your production data is separate from your development data.

So there are two commands to know about. The first one builds your frontend and uploads it to your dev deployment. The second is what that deploy script runs: it builds your frontend with your production Convex URL injected, deploys your backend, and uploads the files to production:

1npx @convex-dev/static-hosting upload --build
2npx @convex-dev/static-hosting deploy
3

Simple enough, right? You'll still want your normal dev server for day to day work tho, as uploading doesn't give you hot reloading.

How does it do this?

If you're anything like me then you're wondering at this point how it does all this. Well it's not actually magic.

When you call the upload or the deploy command it builds your app and bundles it into the dist folder, then it takes all those files and uploads them to the Convex backend. That goes through your Convex CLI login, so random people on the internet can't upload files to your site, even if they know your URL. Here's what that looks like (this one is my agent running it against a throwaway local deployment, hence the local URL):

1$ npx @convex-dev/static-hosting upload --build
2🔒 Using secure internal functions (requires Convex CLI auth)
3Uploading 8 files with deployment ID: 3a6e8c25-ad45-4d40-9633-10541a2c0cd8
4Component: staticHosting
5
6  Generating 8 upload URLs...
7  [1/8] /assets/hero-CLDdwZDr.png (storage)
8  [2/8] /assets/index-D64VDMd1.css (storage)
9  [3/8] /assets/react-CHdo91hT.svg (storage)
10  [4/8] /assets/index-GpEN3EET.js (storage)
11  [5/8] /assets/vite-BF8QNONU.svg (storage)
12  [6/8] /favicon.svg (storage)
13  [7/8] /icons.svg (storage)
14  [8/8] /index.html (storage/html)
15  Staging manifest chunk 1/1...
16  Publishing deployment...
17
18✨ Upload complete!
19
20Your app is now available at: http://127.0.0.1:3423
21

Those files get stored in Convex file storage, and if you open the Data page in the dashboard and pick the staticHosting component from the dropdown, you can see the staticAssets table with its mapping between each path and its stored file:

The staticAssets table in the staticHosting component, with one row per fileThe staticAssets table in the staticHosting component, with one row per file

Then finally it just serves those files from an HTTP action. Pretty neat if you ask me.

Now as I said before, serving files directly from Convex is not the most efficient or cost-effective way of building websites, as file accesses count as function calls and then there's the bandwidth. But it's perfectly functional for a small app like this, and your browser only has to fetch those hashed assets once, as they get cached for a year.

In the video I mention pointing it at a CDN, but the docs now say not to use that option for anything new. If your site gets big enough for this to matter then I'd say that's the time to go and use Vercel or Netlify, which do a heck of a lot more than this little component does and work great with Convex.

Oh and by the way, if you have existing HTTP actions in your Convex project, remember that httpPrefix: "/api" from earlier. It moves them all under /api, so a /hello route becomes /api/hello and doesn't get confused with your site. If you can't move those URLs, because of something like a webhook, then leave the prefixes off in convex.config.ts and register the static routes yourself after your own:

1// convex/http.ts
2import { httpRouter } from "convex/server";
3import { registerStaticRoutes } from "@convex-dev/static-hosting";
4import { components } from "./_generated/api";
5
6const http = httpRouter();
7
8// your own routes go here, they win over the static catch-all
9registerStaticRoutes(http, components.staticHosting);
10
11export default http;
12

Or you can leave your routes alone and mount the site under a path instead, with app.use(staticHosting, { httpPrefix: "/app/" }). You also need to tell Vite about that with base: process.env.STATIC_HOSTING_BASE_PATH ?? "/" in your vite.config.ts, as the CLI sets that for you when it builds.

FAQs

Does it work with Next.js or some other server side framework?

Yes, sort of. You have to build your Next.js project as a static export by setting output: "export" in your Next config, and then tweak the environment variables a bit, as the CLI sets VITE_CONVEX_URL for you and Next wants its own:

1{
2  "scripts": {
3    "build": "NEXT_PUBLIC_CONVEX_URL=${VITE_CONVEX_URL:-$NEXT_PUBLIC_CONVEX_URL} next build",
4    "deploy": "npx @convex-dev/static-hosting deploy --dist out"
5  }
6}
7

This does mean you can't use the server side bits of Next.js, like server rendering or server actions. If you need those then stick with something that runs Next, tho for server actions you could ask why you'd bother when you already have Convex actions.

What if I'm not a fan of that convex.site domain?

Don't worry, Convex does support custom domains for its deployments. As long as you're on a Pro plan or higher you can change it to be whatever you like.

Does client-side routing work?

Our lovely little cats app is a React Vite application that does all its navigation locally, not server side. So when I click on the About page the URL changes to /about, but if I was to refresh the page now... whamp whamp. Watch the address bar in this one (and ignore the little popup in the corner, we'll get to that in a sec):

0:00 / 0:00

Don't worry tho, it's only doing this because I'd turned off the SPA fallback for this demo. Leave it at the default and a refresh loads your about page as expected, so client-side routing just works out of the box. I did that with a setting in http.ts in the video, but these days if you really do want real 404s then you deploy with a --no-spa flag instead.

By the way, you might have noticed the cats are different in that clip. That's my production deployment, so it has its own data. Noice!

Can I use this in CI?

Yes you can. I'm using it on lots of different projects at the moment and I generally have them set up to build and deploy from GitHub Actions. It's kind of the same thing as a normal Convex deployment setup, which is that you need a deploy key. You can grab that from the Convex dashboard, or you can use the new handy dandy CLI command to generate one:

1npx convex deployment token create ci-token --prod
2

Then pop that key into your GitHub Actions secrets as CONVEX_DEPLOY_KEY and have your agent build you a workflow script that runs npx @convex-dev/static-hosting deploy. Simples!

Can you let users of your running app know there's a new version?

Yes you can, and it's a really cool feature that in the past I've had to build manually myself. To get it to work you just need to expose the current deployment with a query:

1// convex/staticHosting.ts
2import { exposeDeploymentQuery } from "@convex-dev/static-hosting";
3import { components } from "./_generated/api";
4
5export const { getCurrentDeployment } = exposeDeploymentQuery(
6  components.staticHosting,
7);
8

Then the component comes with this React component that you can just drop into your application:

1import { UpdateBanner } from "@convex-dev/static-hosting/react";
2
3<UpdateBanner message="Fresh cats are ready." buttonText="Reload" />;
4

It looks at whatever the current deployment is, and when that changes it lets your users know there's a new version and prompts them to refresh. That's the popup from the last clip, down in the bottom right here:

The cats app with the "Fresh cats are ready." popup and its Reload button in the bottom rightThe cats app with the "Fresh cats are ready." popup and its Reload button in the bottom right

Very nice!

Conclusion

So just to recap: install the component, run setup, run npm run deploy, and your frontend is live on your Convex deployment, with separate dev and production and client-side routing working out of the box.

I think what I like most is that I don't have to go and log into another dashboard. It's all just another command on the same project my agent is already working in, so it can do the whole thing for me.

If you're upgrading from 0.1 there's a migration guide, as you have to re-upload your files. And the README has everything else.

Until next time, cheerio! 👋

All gas, no breakages

Convex is the reactive backend platform that keeps up with you and your agents. Database, functions, workflow, sync, search, file storage, and more. All TypeScript, zero glue.

Get started