How to implement custom domains for pages in NextJS.
Tags: nextjs, javascript, webdev, customdomains
Published: 2024-01-29 07:50:12

I'm a full-stack developer.

when I was looking at some support tickets I saw that most users were asking for a custom domain for their landing pages.

I started researching on Google and asked chatgpt, but the resources were not specific to my problem statement, they were just okay.

My first open-source contribution was to the EddieHub organization.

I casually revisited one of their projects - BioDrip, they had custom domains for user profiles, where normally the user profiles were just a landing page.

I was so lucky.
The tech stack for makelanding.ai and BioDrip were the same - NextJS.

I started reading the [pull request](https://github.com/EddieHubCommunity/BioDrop/pull/9436) for the custom domain feature.

Technically, having a custom domain for pages is impossible - a discord user replying to my help post.

The custom domains actually point at the IPv4 address of makelanding.ai - 76.76.21.21.

If the users wanted a subdomain for their landing page then they had to add a CNAME record with value cname.vercel-dns.com.

NextJs [middleware](https://nextjs.org/docs/pages/building-your-application/routing/middleware) which runs on the edge runtime can be used for many purposes:

- A/B testing landing pages
- Authentication
- Geo-specific content delivery
- Authentication
- Personalization, which is the same as Geo-specific content delivery
- Rewriting the response, which I used to serve the pages on custom domains.

When a user adds a custom domain to their landing page, It's added to the domains of the Vercel project, and I'm just verifying if the domain has configured the DNS records correctly using this vercel API: `https://api.vercel.com/v10/domains/${domain}/config?teamId=${process.env.VERCEL_TEAM_ID}`

To create the middleware in a NextJs project you just have to create a `middleware.js` or `middleware.ts` file in the root folder.

By default, the middleware will run on all the routes, API, and pages, but you can specify the routes, where you want to run the middleware by a config variable export:

```
export const config = {
    matcher: [
        "/",

        // page management
        "/page/:path*",
        "/api/page/domain",
    ],
};
```
You don't want to run middleware on API routes where you're using dependencies, because middleware runs on edge runtime and to be fast it is like a minified version of nodejs, its edge runtime only supports few web APIs and the max size for a route's executed code running on edge is between 1MB - 4MB.

It'd be better to just visit the NextJs [middleware](https://nextjs.org/docs/pages/building-your-application/routing/middleware) doc rather than understand my explanation.

How this works is when the custom domain has configured the DNS records correctly and it's pointing to the vercel deployed project, the middleware checks the host which is making the request to the project's IPv4 address.

```
const hostname = req.headers.get("host");
    const reqPathName = req.nextUrl.pathname;
    const hostedDomain = process.env.NEXT_PUBLIC_BASE_URL.replace(
        /http:\/\/|https:\/\//,
        "",
    );
    const hostedDomains = [hostedDomain, `www.${hostedDomain}`, `preview.${hostedDomain}`];

    const regex = /^(.*\.)?vercel\.app$/;

    const vercelSubdomain = regex.test(hostname);

    // if custom domain + on root path
    if (!hostedDomains.includes(hostname) && !vercelSubdomain && reqPathName === "/") { 
    console.log("custom domain for landing page")
}
```
If the host is not the hostedDomain for the vercel project or the subdomain provided by vercel for the projects, then we proceed.

Then check if the custom domain exists in the database, get the corresponding landing page to that specific custom domain, and rewrite the response! Easy!

```
if (
            page?.domain &&
            page?.domain === hostname
        ) {
            console.log(
                `custom domain "${hostname}", matched for page "${page.name}" (protocol: "${protocol}")`,
            );
            //If a match is found, rewrite to the custom domain and display the page
            return NextResponse.rewrite(
                new URL(
                    `/page/${page.id}`,
                    `${protocol}://${page.domain}`,
                ),
            );
        } else {
            console.error("something went wrong with serving the page on custom domain: " + page.domain)
        }
```