How to Build a Multi-Tenant App with Custom Domains Using Next.js
Building an app with Vercel / Next
Table of contents
Test the app here
In this guide, you'll learn how to build a full-stack multi-tenant application by using the Platforms Starter Kit and the following technologies:
Next.js as the React framework
Tailwind for CSS styling
Prisma as the ORM for database access
PlanetScale as the database (MySQL)
NextAuth.js for authentication
Vercel for deployment
If you already have an existing project and only want to see the steps for multi-tenancy, skip ahead to steps 4 and 5. Also, the code for this app can be found here.
1. Set up your Next.js project
We’ll be using the Platforms Starter Kit to kickstart our Next.js project.
First, open up your terminal and navigate and run the following:
npx create-next-app --example https://github.com/vercel/platforms/tree/main platforms
This will create a new folder in your current directory called platforms
. Then, you can navigate into the folder, install the dependencies, and launch the app:
cd platforms && npm i && npm run dev
The new application has the following structure:
pages
└───api
└───app
│ │ index.tsx
│ │ login.tsx
│ │ settings.tsx
│ │
│ └───post
│ │ │ ...
│ │
│ └───site
│ │ ...
│
└───home
│ │ index.tsx
│
└───_sites
│ │
│ [site]
│ │ │ index.tsx
│ │ │ [slug].tsx
Aside from the /api
folder, there are 3 main folders in the /pages
directory:
/app
: All routes for the app subdomain (app.example.com
), where users can customize their individual content pages./home
: All routes for the landing page (example.com
)./_sites
: All routes for all user content pages (e.g.john.example.com
,kate.example.com
).
These folders contain the basic app structure for a multi-tenant app. However, only the /home
route works for now. Let’s continue by adding our database.
Note: Don't forget to convert the .env.example
file that’s located at the root of the repo into a .env
file – it'll come in handy later.
2. Set up your MySQL database
Prerequisite: You need to have the PlanetScale CLI installed
Create a new account with PlanetScale.
Using the PlanetScale CLI, create a new database called
platforms
.pscale db create platforms
Next, connect to the database branch:
pscale connect platforms main --port 3309
In a different terminal window, use the
db push
command to push the schema defined inprisma/schema.prisma
:
// pscale auth login -> run in terminal
npx prisma db push
Now that the initial schema has been added, promote your
main
branch to production:pscale branch promote platforms main
You've just provisioned your database! Now, when you go to app.localhost:3000, you should see the following screen:
To push additional schema changes to your database, follow the instructions from PlanetScale on Prisma Migrations.
Don't forget to get your production MySQL
DATABASE_URL
from your Planetscale database – you'll need it for when you deploy your app to Vercel later. You can do that by following this guide. Note that yourDATABASE_URL
should be in the following format:mysql://<USERNAME>:<PASSWORD>@<HOST>/<DATABASE>?sslaccept=strict
3. Set up user authentication with next-auth
Now, let’s add authentication to allow users to create accounts, add new sites, and add custom domains.
We will use the next-auth
library for authentication. This example is preconfigured to use GitHub OAuth. All user data is stored in your PlanetScale database, based on the Prisma schema defined.
To set up GitHub for authentication:
Go to Developer Settings on GitHub.
Click on "New GitHub App".
Name your GitHub App. In our example, we'll call it "Platforms Starter Kit (dev)".
Add your homepage URL (or a placeholder, if you don't have a website yet).
For the "Callback URL" field, put
http://app.localhost:3000
. Since GitHub only allows one callback URL per app, we have to create separate apps forlocalhost
and production (hence the "dev" name in step 3).If the "Active" field under "Webhook" is checked, uncheck it. Now, click on "Create Github App".
Once your app is created, you should see the following screen. Click on "Generate a new client secret":
Copy the client secret you generated and paste it under the
GITHUB_SECRET
value in your.env
file:Copy the Client ID and paste it under the
GITHUB_ID
value in your.env
file:
You're all set! You can now go back to the app login page and log in with GitHub.
4. Configure rewrites for multi-tenancy
Vercel Edge Functions give you the benefits of static with the power of dynamic. Inside this template, we use Middleware to create flexible rewrite rules.
First, navigate to the _middleware.js
file at the root of your /pages
folder. Inside this file, we have set up rewrite rules to map each subdomain/custom domain to their dynamic routes in /pages
.
You’ll need to replace all instances of vercel.pub
with your own domain to configure rewrites correctly. If you don't have a custom domain, you can add the .
vercel.app
domain that your project was assigned and use that as your custom domain.
5. Deploy to Vercel
Next, we’ll be deploying the repo to Vercel. Note that this is not the final step since we’ll still need to configure the feature for adding custom domains in the next step.
First, create a new Github repository and push your local changes.
Deploy it to Vercel. Ensure you add all Environment Variables in your
.env
file to Vercel during the import process.Ensure you create another GitHub App for production environment as well, and then add the production callback URL as an Environment Variable. It should be in the following format:
https://app.<YOURDOMAIN.COM>/api/auth/callback/github
.Fill in the
SECRET
token in the.env
file and add that as an Environment Variable.Add the production
DATABASE_URL
you retrieved in step 2 as an Environment Variable.
In your Vercel project, add your root domain & wildcard domain.
When adding your custom domain, ignore the recommended step to "add the
www.
version of your domain and redirect your root domain to it" – just add the root domain.To set up wildcard domains, you'll need to add the domain using the Nameservers method (as opposed to the recommended A records method).
6. Add custom domains with the Vercel API
We’ll use the Vercel API to add custom domains to your project and assign it to the user's account:
Get your
AUTH_BEARER_TOKEN
env var from your Vercel Accounts Settings page under Tokens. Add that value to your.env
file under theAUTH_BEARER_TOKEN
key.Get the
PROJECT_ID_VERCEL
env var for your project fromhttps://vercel.com/<TEAM_OR_USER_NAME>/<PROJECT_SLUG>/settings
. It should be in the formatprj_xxxxxx...
If you're deploying your project under a team account, you'll also need to get your
TEAM_ID_VERCEL
env var – this can be found athttps://vercel.com/teams/<TEAM_SLUG>/settings
We’ve set up a few API routes to help you handle custom domains:
/api/domain
(POST
): Add domains to your Vercel project using this endpoint whenever a user adds it on your platform. This returns 3 possible outcomes:Status code
403
: The domain is already owned by another team but you can still request delegation from the team and add it.Status code
409
: The domain is already being used by a different project. You can’t add it unless the domain is removed from the project.Status code
200
: The domain is successfully added.
/api/domain/check
: Checks if a domain has been successfully configured for your project. Returns two possible values:true
: domain has been configured successfullyfalse
: domain has not been configured correctly
/api/request-delegation
: If a domain is owned by another team, you can use this endpoint to request delegation from the team and add it. Caveat: we are actively working on improving our domain delegation process and it’s likely that there won’t be the need for this endpoint in the future/api/domain
(DELETE
): Removes domains from your Vercel project using this endpoint whenever a user removes it from your platform.
Note: The /api/domain
POST
endpoint only adds the root domain. If you want to add the www.
subdomain and redirect it to the root, you'll have to perform an additional API call.
7. Additional Features
Here are some supplementary code snippets that might be required to build Platforms on Vercel:
Static Tweets
Avoid Cumulative Layout Shift (CLS) from the native Twitter embed by using our static tweets implementation (supports image, video, gif, poll, retweets, quote retweets, and more).
You'll need a Twitter auth bearer token, which you'll paste into the TWITTER_AUTH_TOKEN
field in your .env
file. Here's how you can get a Twitter auth bearer token.
Image Uploads with Cloudinary
Cloudinary is used to handle image uploads. Here’s the reusable component we created and here’s the code we used to generate a blurhash from the uploaded images.
You'll need a Cloudinary cloud account (cloudName
) and a Cloudinary upload preset (uploadPreset
). Here's how you can get those variables set up.
Conclusion
In this guide, you learned how to build a full-stack multi-tenant application by using the Platforms Starter Kit. From blogging platforms to low-code tools, this starter kit can be a starter kit for a number of different types of applications, we’re excited to see what you build!
If you run into any issues or have any questions about this guide, feel free to raise them on GitHub or drop them in the Next.js Discord.