Logo Projects Blog

Subdomains in Nuxt 3

The Nuxt logo

While working on a refactor of a Nuxt 2 project to Nuxt 3 I came across an issue while trying to use subdomains with Nuxt 3. For the Nuxt 2 project I had used the @nuxtjs/router package to create a custom router that would filter out the pages that I only wanted to show on a subdomain. This package was not updated to work with Nuxt 3, so I had to find a different solution.

In the docs there is a short explanation on how to extend the default router of Nuxt. Using the code snippet listed there we can create a custom router that will filter out the pages that we only want to show on a subdomain:

/* src/app/router.options.ts */

import { RouteRecordRaw } from 'vue-router'

import type { RouterConfig } from '@nuxt/schema'

const SUBDOMAINS = ['dashboard', 'admin'] as const

// Replace the available routes with only the routes that are available on that subdomain.
function subdomainRoutes(subdomain: string, defaultRoutes: RouteRecordRaw[]) {
  const subdomainRoutes: RouteRecordRaw[] = []

  for (const defaultRoute of defaultRoutes) {
    if (defaultRoute.path.startsWith(`/${subdomain}`)) {
      // Copy the route to ensure we don't modify the default route.
      const routeCopy = Object.assign({}, defaultRoute)
      routeCopy.path = routeCopy.path.split(`/${subdomain}`)[1]

      // If the path is empty, replace it with a slash for the root path.
      if (routeCopy.path === '') {
        routeCopy.path = '/'
      }

      subdomainRoutes.push(routeCopy)
    }
  }

  return subdomainRoutes
}

export default <RouterConfig>{
  routes: _routes => {
    // Copy the array of routes to remove the readonly modifier.
    const mutableRoutes = [..._routes]
    const nuxtApp = useNuxtApp()

    let host: string | undefined = undefined

    if (process.server) {
      host = nuxtApp.ssrContext?.event.node.req.headers.host
    } else {
      host = window.location.host
    }

    if (!host) {
      return null
    }

    for (const subdomain of SUBDOMAINS) {
      // Here we check if the host starts with a subdomain we have defined.
      if (host.startsWith(`${subdomain}.`)) {
        return subdomainRoutes(subdomain, mutableRoutes)
      }
    }

    return null
  }
}

The subdomain routes are now working on their respective subdomain, but it seems we have broken the default routes. To fix this we can filter out the subdomain routes from the default routes so we are left with the routes that should be shown on the main domain. We can do so using the following function:

// Filter out pages that we only want to show on a subdomain
function filterSubdomainRoutes(routes: RouteRecordRaw[]) {
  return routes.filter(route => {
    for (const subdomainKey of SUBDOMAINS) {
      if (route.path === `/${subdomainKey}` || route.path.startsWith(`/${subdomainKey}/`)) {
        return false
      }
    }

    return true
  })
}

We can now use this function to filter out the subdomain routes from the default routes instead of returning null:

export default <RouterConfig>{
  routes: _routes => {
    ...

    if (!host) {
      return filterSubdomainRoutes(mutableRoutes)
    }

    for (const subdomain of SUBDOMAINS) {
      ...
    }

    return filterSubdomainRoutes(mutableRoutes)
  }
}

Conclusion

With the bits of code added above we can now show different routes based on the subdomain that is being used. This is a simple solution that can be expanded upon to add more complex routing based on the subdomain.

You can view the full code in this gist.