DEV Community

Ian Macartney
Ian Macartney

Posted on

Multiple apps on a single domain hosted on sub-paths

tl;dr It's possible to deploy multiple apps on the same domain by serving each at a different sub-path, using a simple vercel.json configuration, and custom build commands.

Even a static SPA React app with client-side routing can be configured to be served at a custom path.

When developing my llama farm chat demo, I had been developing it like a normal app, with the index at / on http://localhost:5173/, but then needed to host the app entirely under the /llama-farm sub-path at labs.convex.dev/llama-farm, since labs.convex.dev also hosts auth, Ents, a million checkboxes clone and other independent projects. It uses a client-side router (React Router), which needs all routes to get redirected to the same index.html, and each page needs to get served relative to that subpath, without littering the codebase with a prefix for every route.

In this post I'll cover:

  1. Hosting your Vite-based React app with client-side router under a sub-path on Vercel.
  2. Hosting multiple apps on subpaths within a single project.
  3. Configuring multiple sub-paths for the same app in a single vercel.json.

Serving on a subpath

To change my app to work on a sub-path on Vercel, the four changes that I needed to make were:

  1. Setting the public base path by specifying --base=/llama-farm in my vite build command.
  2. Setting the build.outDir to --outDir=dist/llama-farm so it would create the assets at the relative path Vercel expected. You can see the full build command below.
  3. Setting the basename field for React Router on createBrowserRouter to { basename: "/llama-farm" }. In order to test locally at / I made this an environment variable VITE_BASEPATH that I only set in Vercel. You can see my code here.
  4. Adding a rewrites configuration to my vercel.json file:
{
  "rewrites": [
    {
      "source": "/llama-farm(.*)",
      "destination": "/llama-farm"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

On Vercel my build command ended up looking like:

vite build --base=$VITE_BASEPATH --outDir=dist$VITE_BASEPATH
Enter fullscreen mode Exit fullscreen mode

I set the VITE_BASEPATH environment variable to /llama-farm so it was shared between the build command and React Router config.

Adding in Convex

One more layer is if our app uses for the backend, in which case we can also deploy our Convex backend at the same time. For this, we wrap our build command with npx convex deploy --cmd '<build command here>'.1 This will build the Vite app with the VITE_CONVEX_URL set, which tells the frontend where to find the corresponding backend. This is especially useful for preview deploys which each have their own backend. As an additional safeguard, if the build fails it won't deploy your backend.

The full updated build command:

npx convex deploy --cmd 'vite build --base=$VITE_BASEPATH --outDir=dist$VITE_BASEPATH'
Enter fullscreen mode Exit fullscreen mode

Now my chat app is being served on llama-farm-chat.vercel.app/llama-farm. In the next section I'll connect it to the app running labs.convex.dev so I can access it on labs.convex.dev/llama-farm.

Serving multiple supaths from another project

To serve my app from the labs.convex.dev app, I'll edit the vercel.json for the Convex Labs project to handle redirecting and rewriting requests to my app.

  • redirects will serve assets from the same relative path, but from elsewhere. In my case the .js and .css files are in the llama-farm/assets/ directory.
  • rewrites will take into account the rewrites in my llama farm project, both for the base url /llama-farm and any subpaths. These will all get rewritten to /llama-farm which will serve the index.html file there.
{
  "redirects": [
    {
      "source": "/llama-farm/assets/:filePath",
      "destination": "https://llama-farm-chat.vercel.app/llama-farm/assets/:filePath",
      "permanent": true
    },
    //... other projects
  ],
  "rewrites": [
    {
      "source": "/llama-farm",
      "destination": "https://llama-farm-chat.vercel.app/llama-farm"
    },
    {
      "source": "/llama-farm/:match*",
      "destination": "https://llama-farm-chat.vercel.app/llama-farm/:match*"
    },
    //... other projects
  ]
}
Enter fullscreen mode Exit fullscreen mode

You can read more about redirects and rewrites in the Vercel docs.

Configuring multiple subpaths

You might want to build the same project and host it on different sub-paths on different domains. For example, I also host my app at llamafarm.chat.

I'll start by saying there are many Vercel community discussions about how using environment variables to configure vercel.json is not supported. The recommendation is to use edge function middleware or a Next.js config. For this app I'd rather not pay for serverless functions or migrate it to Next.js.

However, I have a configuration that is working for me. I've found that I can put both rewrite configs into one vercel.json and both apps will route correctly.

vercel.json:

{
  "rewrites": [
    {
      "source": "/llama-farm(.*)",
      "destination": "/llama-farm"
    },
    {
      "source": "/(.*)",
      "destination": "/"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

On labs.convex.dev/llama-farm, it makes sense that it will only ever match against incoming urls starting with /llama-farm since the labs.convex.dev project only rewrites those urls.
However, I'm a bit confused why llamafarm.chat correctly routes /llama-farm to /index.html instead of looking for /llama-farm. My guess is that it can't find any /llama-farm/index.html at build time, so ignores the first rule for that project. If you know more, please reach out! I'm just happy that it works.

Summary

We looked at hosting a client-routed SPA app (in my case a Vite app using React Router) on a base path, serving multiple apps on the same project, and configuring the rewrite paths for multiple environments in the one vercel.json.

I hope it helps you out! Come chat in our community Discord if you have any comments or feedback.


  1. Note when pointing multiple Vercel projects at one Convex backend: I choose to only deploy the Convex backend from one project, and just do the frontend-only deploy from the other. For the one that is only deploying the frontend, I set the CONVEX_SITE_URL environment variable instead of the CONVEX_DEPLOY_KEY. This means that all frontend preview deployments for this project are talking to the prod backend. 

Top comments (0)