In a similar post I wrote 2 years ago, I mentioned that only Next.js has built-in support for multi-tenancy, but that’s not true anymore, SvelteKit has caught up to the competition and now offers a way to build multi-tenant apps.
In this post I’ll revisit the topic and add a few more frameworks to compare. Let’s start with the OG.
Next.js has largely remained the same in terms of multi-tenancy support, you use Next.js middleware to reroute requests based on the host
header. It’s pretty straightforward:
What the code above does:
host
is equal to OUR_DOMAIN
, if not it belongs to a tenant, the value of tenant
is the subdomain or custom domain_tenant/$tenant/$originalPath
if it isSvelteKit has a concept of hooks
that will be called in response to specific events, now there’s a reroute
hook that allows you to reroute requests based on incoming URL.
Here’s how you can use it for multi-tenancy:
The Next.js version is all done in a server-side middleware, I didn’t read their code but for sure there’s some magic going on behind the scenes to pass the new URL to the client, so that the client-side router can pick it up. SvelteKit is more explicit about it, reroute
is a universal hook which means it also runs in the browser, so the client-side router can use it to get the desired URL.
Unfortunately, Remix still doesn’t have built-in support for multi-tenancy as of writing, they have some proposals though like Lazy Route Discovery and Route Segment Constraints which could help implement this feature.
Until then you can either split your main app and user app into different projects inside a monorepo, or conditionally render different components in the same route based on the host
header using loader
function:
It’s kinda verbose since you have to do this in every route, I personally would rather split it into different projects instead.
Solid Start is the new kid on the block, it’s so new that I didn’t expect it to have this kind of flexibility for multi-tenancy, but it does! And surprisingly you can do it within a Solid component.
Let’s take a look at the root layout component in Solid Start:
The FileRoutes
component caught my eyes immediately, I’m sure it caught yours too because I highlighted it for you. After reading the source code I found out that it returns the generated routes as an array. This means we can manipulate the routes before passing them to the Router
component as children.
Now here’s another problem, I need the host
header or the request URL on both server-side and client-side to determine the tenant, how do I do that in a component?
Thanks to Node.js AsyncLocalStorage
API, Solid Start uses it to provide the getRequestEvent
function which you can use in any component to get the host
header:
Now you’ve got the tenant
value, you can use it to map different routes to your main app and tenant app. I also chose to put them under different folder in src/routes
:
src/routes/app
for the main app routessrc/routes/tenant
for the tenant app routesWithout midifying the FileRoutes
component, your website will have /app/*
and /tenant/*
routes which is obviously not what you want. So you have to filter the routes and modify route path in a custom FileRoutes
component:
Now just replace the FileRoutes
component in the App
component with CustomFileRoutes
and you got a solid multi-tenant app!
Last but not least, Nuxt, famous for its flexibility and rich plugin ecosystem. So of course there’s a plugin for multi-tenancy, and yes I found one https://github.com/hieuhani/nuxt-multi-tenancy.
However this plugin is only useful if it’s a limited set of subdomains or custom domains known at build time, meaning it won’t work for your SaaS app where users can add their own custom domain. But you can have your own implementation quite easily since Nuxt allows to modify the routes at runtime.
First, you can get request host
in Nuxt using useRequestURL
to determine the tenant:
Then in the router.options.ts
file, you can normalize the routes based on the tenant, similarily I have pages/app/
folder the main app and pages/tenant/
for tenant app routes:
The easiest solution to multi-tenacy is to have the framework handle it for you through a reroute mechanism, like Next.js and SvelteKit, in that way you can also map the tenant
value to a route parameter [tenant]
.
Nuxt and Solid Start require a bit more work, since you need to modify the routes yourself.