How to Build a Multi-Tenant App with Custom Domains Using Next.js

Create a full-stack application with multi-tenancy and custom domain support using Next.js App Router, Vercel Postgres, and the Vercel Domains API.
Last updated on December 4, 2024
Build, Deployment & GitDatabases & CMS

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:

You can find the code for this app on the Platforms Starter Kit Github page.

In this guide, we look at 2 ways to deploy this application:

At the end of this tutorial, you'll get a powerful full-stack application with the following features:

  1. Multi-tenancy – programmatically assign unlimited custom domains, subdomains, and SSL certificates to your users
  2. Ultra-performant blog posts cached via Vercel's Edge Network, with the ability to invalidate the cache on-demand (when users make changes) using Incremental Static Regeneration.
  3. AI powered Markdown editor for a Notion-style writing experience
  4. Drag & drop / copy & paste image uploads
  5. Custom fonts, 404 pages, favicons, sitemaps for each site
  6. Dynamic Open Graph images for each blog post
CleanShot 2023-07-05 at 08.39.10.png
CleanShot 2023-07-05 at 08.39.02.png

  • Click the deploy button on the Platforms Starter Kit template page
  • Choose where you would like to clone the repository and click Create

In the Add Storage section, select the Postgres Database option that is available to you. The Vercel integration will create and provision a Postgres database, connect it to your new project and add the required environment variables

In this section, you will create the values of the following required environment variables:

NEXT_PUBLIC_ROOT_DOMAIN: This is the root domain of your app such as mydomain.com, okta.com, hashnode.dev, etc.

NEXTAUTH_SECRET, AUTH_GITHUB_ID & AUTH_GITHUB_SECRET: These are the secrets required for GitHub OAuth and login via NextAuth.js.

  • You can use the GitHub OAuth clients generator to create values for AUTH_SECRET, AUTH_GITHUB_ID & AUTH_GITHUB_SECRET. When pasting them, you will need to use the value of AUTH_SECRET for NEXTAUTH_SECRET.

AUTH_BEARER_TOKEN: This is your Vercel authentication token.

PROJECT_ID_VERCEL: This is your Vercel Project ID that can be found in your Vercel Project settings page under Project Name

  • Since you haven't deployed your project to Vercel yet, use a test value like my-new-project.

TEAM_ID_VERCEL: This is your Vercel Team ID that can be found in your Vercel Team Settings page under General.

OPENAI_API_KEY: This is used for the AI text generation in the text editor.

  • From a new or existing OpenAI account, generate a key from the API keys section.

Click the Deploy button. Once your project is deployed, you will be taken to the project's deployment page.

Update the PROJECT_ID_VERCEL environment variable.

Update your Project's domain settings

  • Add the root domain which will be the same value as NEXT_PUBLIC_ROOT_DOMAIN such as mydomain.com. Ignore the recommended step to "add the www. version of your domain and redirect your root domain to it".
  • Add a wildcard domain, such as *.mydomain.com to handle the creation of sites with any subdomain under your main domain. Your domain needs to be connected to Vercel with the nameservers method for this to work.

Update the callback URLs

For the GitHub App created by the GitHub OAuth clients generator:

  • Go to Developer Settings on GitHub and click on the GitHub app that you created.
  • Under the Identifying and authorizing users section, add the following 2 callback URLs:
  • http://app.localhost:3000/api/auth/callback/github (for local development)
  • https://app.<YOURDOMAIN.COM>/api/auth/callback/github

Test your application

  • Redeploy your production deployment and visit app.mydomain.com.
  • Log in with your Github account and create a site under Sites.
  • Test that the sub site with a new subdomain works.

We’ll be using the Platforms Starter Kit repository to kickstart our Next.js project.

First, open your terminal and run the following:

npx create-next-app --example https://github.com/vercel/platforms platforms

This will create a new folder in your current directory called platforms. Note that you might run into dependency tree resolving issues during this step, but you can safely ignore it.

Once this step is complete, navigate into the folder and launch the app with pnpm:

cd platforms && pnpm i && pnpm dev

The new application has the following structure:

app/
api/
app/
(auth)
login/page.tsx
layout.tsx
(dashboard)
post/[id]
settings/
site/[id]
sites/
layout.tsx
loading.tsx
page.tsx
home/
page.tsx
[domain]
[slug]/
not-found.tsx
opengraph-image.tsx
page.tsx
layout.tsx
page.tsx

Aside from the /api folder, there are 3 main folders in the /app directory:

These folders contain the basic app structure for a multi-tenant app and leverages the latest Next.js App Router features like Server Actions, revalidateTag, dynamic OG images, etc.

To run this app, we'll need to set up the required environment variables.

To do that, convert the .env.example file that’s located at the root of the repo into a .env file.

Here are the set up instructions for each of the environment variables:

  • NEXT_PUBLIC_ROOT_DOMAIN: This is the domain of your app (e.g. vercel.app, okta.com, hashnode.dev etc.)
  • POSTGRES_URL: Follow the getting started steps to provision a Marketplace Postgres instance. If you have already deployed the template, you can pull the env vars with the Vercel CLI by running vc env pull (Review the vercel env CLI docs).
  • BLOB_READ_WRITE_TOKEN: Follow the Vercel Blob quickstart to provision a Vercel Blob instance. If you have already deployed the template, you can pull the env vars with the Vercel CLI by running vc env pull.
  • NEXTAUTH_SECRET, AUTH_GITHUB_ID & AUTH_GITHUB_SECRET: These are the secrets required for GitHub OAuth and login via NextAuth.js. You can use the GitHub OAuth clients generator to create values for AUTH_SECRET, AUTH_GITHUB_ID & AUTH_GITHUB_SECRET. When pasting them, you will need to use the value of AUTH_SECRET for NEXTAUTH_SECRET.
  • AUTH_BEARER_TOKEN: This is your Vercel authentication token. You can use an existing one or create a new one from your Account Tokens page.
  • PROJECT_ID_VERCEL: This is your Vercel Project ID that can be found in your Vercel Project settings page under Project Name. Since you haven't deployed your project to Vercel yet, use a test value like my-new-project.
  • TEAM_ID_VERCEL: This is your Vercel Team ID that can be found in your Vercel Team Settings page under General.
  • OPENAI_API_KEY: This is required for AI text generation in the text editor. From a new or existing OpenAI account, generate a key from the API keys section.
Note: When developing locally, don't forget to add the NEXTAUTH_URL environment variable in your .env file and set the value to http://app.localhost:3000. You won't need to do this in production since NextAuth automatically detects the URL from your Vercel environment.

The template comes pre-populated with the necessary Prisma schema for this app. All you need to do to publish this schema to your database is run the following command:

npx prisma db push

Note: If you have already cloned and deployed the template, you can skip this step, since the build script automatically runs prisma db push for you.

In step 2, we set up a GitHub app using the GitHub Client Generator.

However, we still need to customize the callback URLs for the GitHub App in order for the OAuth process to work properly

First, go to Developer Settings on GitHub and click on the GitHub app that you created earlier. Then, under the "Identifying and authorizing users" section, add the following 2 callback URLs:

  1. http://app.localhost:3000/api/auth/callback/github
  2. https://app.<YOURDOMAIN.COM>/api/auth/callback/github
    CleanShot 2023-06-29 at 21.25.13.png

Save your changes, and you're all set! You can now go back to the app login page and log in with GitHub.

CleanShot 2023-06-29 at 21.31.33.png

Finally, we’ll be deploying the repo to Vercel.

1. First, create a new Github repository and push your local changes.

2. Deploy it to Vercel. Ensure you add all environment variables that you configured earlier to Vercel during the import process.

3. In your Vercel project, add your root domain & wildcard domain.

  • Add the root domain which will be the same value as NEXT_PUBLIC_ROOT_DOMAIN such as mydomain.com. Ignore the recommended step to "add the www. version of your domain and redirect your root domain to it".
  • Add a wildcard domain, such as *.mydomain.com to handle the creation of sites with any subdomain under your main domain. Your domain needs to be connected to Vercel with the nameservers method for this to work.
8.png

That's it – you can now go to app.<YOURDOMAIN.COM>, log in, and start creating some sites!

Throughout this process, there are several caveats that you should pay attention to:

You might've noticed that we are generating two different sites for subdomains and custom domains. There are a few ways you can improve on this:

  • Redirect subdomain to custom domain if exists: We actually have a section in the code that lets you do that.
  • Setting a canonical URL that points to the custom domain if it exists (refer code).

As a platform owner, you might want to allow your users to add www. subdomains along with their apex domain (and have the former redirect to the latter).

To achieve that behavior, you will need to make an additional API call to the Vercel Domains API to add the www. version for a domain and set a redirect attribute to make it redirect to the apex domain.

When pushing changes to your multi-tenant sites, you might want to preview them using Vercel Preview URLs. We are currently beta testing a feature for you to do that via Preview Deployment Suffixes:

  • Preview URL: <preview-subdomain>.yourdomain.dev
  • Multi-tenant preview URL: <tenant-slug>---<preview-subdomain>.yourdomain.dev

Here's an example using the demo platforms app:

This feature is currently available for Enterprise customers on Vercel – if you're interested in testing it out, please reach out to your Customer Success Manager (CSM) and we will enable it for your preview deployment suffix.

Please note, there is also a 63 character limit so long branch URLs (including the <tenant-slug>--- section) may fail to resolve. To resolve this, you should use shorter branch names.

If you're using Google as an OAuth provider, you might run into an issue where Google does not allow you to add localhost subdomains as an Authorized redirect URI:

272703853-bacefbd2-67e6-439a-a27d-2606e0a6b690.jpg

This is a known limitation – to fix this, you can just use a different localhost port (e.g. localhost:8888) in lieu of app.localhost:3000. You will need to update your NEXTAUTH_URL environment variable to reflext this change.

You might notice that the analytics section of each site is filled with dummy data (with beautiful charts powered by Tremor):

CleanShot 2023-06-29 at 22.04.05.png

We highly recommend using a service like Tinybird to ingest, store, and visualize pageview data for each of your sites. In fact, they have an open-source starter kit that shows how you can set this up in less than 5 minutes – we highly recommend trying it out!

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.

Couldn't find the guide you need?