Two different subdomains in one Next.js routing

You can't split 1 Next.js app between sub-domains for several reasons. From an experience, I had a similar requirement (3 areas) I started with one app split into 3 (using sub paths)

  1. Assets (css, js libs) leaks between "areas".
  2. one big app with 3 areas means, each change will require re-deploy all the areas (one big deployable)
  3. Build time, to build 3 areas will be much longer.
  4. Each area may introduce different requirement, such as, UI components for admin area, but custom ui components for the "front" area, Auth, translations and many more

Ended up with 3 separate Next.js apps which managed inside yarn workspaces and get deployed by a specific area.

After I've explained my experience, you can achieve a setup with a reverse-proxy such as nginx to map sub-domain to subpath in your next app.

Let's say you have 3 areas, front, admin, users.

www.domain.com/some-page => should be mapped to localhost:3000/front/some-page. users.domain.com/some-page => should be mapped to localhost:3000/users/some-page. admin.domain.com/some-page => should be mapped to localhost:3000/admin/some-page.

// www.domain.com.conf 

server {
    listen       80;
    server_name  www.domain.com;
    access_log   /var/log/nginx/access.log  main;
    root         html;
 
    location / {
      proxy_pass   http://127.0.0.1:3000/front/; // <-- the last slash is important
    }

  }
// users.domain.com.conf

server {
    listen       80;
    server_name  users.domain.com;
    access_log   /var/log/nginx/access.log  main;
    root         html;
 
    location / {
      proxy_pass   http://127.0.0.1:3000/users/; // <-- the last slash is important
    }

  }

Pay attention

  1. you will need to rewrite static assets as well.

I manage to create subdomains using a custom express server. This is a blank app with no assets, I haven't tried this yet on a real app with assets (CSS, images, etc)

I have the following pages folder structure:

pages/
├── admin/
│   ├── index.js
│   └── sample-page.js
└── member/
    ├── index.js
    └── accounts/
        └── dashboard.js

When you are using next dev which is the default. This will produce the following routes:

  • http://lvh.me:3000/admin
  • http://lvh.me:3000/admin/sample-page
  • http://lvh.me:3000/member
  • http://lvh.me:3000/member/accounts/dashboard

But using the custom server.js file and running our dev server using node server.js this will produce the following routes:

  • http://admin.lvh.me:3000
  • http://admin.lvh.me:3000/sample-page
  • http://lvh.me:3000
  • http://lvh.me:3000/accounts/dashboard

The content of our server.js file:

const express = require('express')
const next = require('next')
const vhost = require('vhost')

const port = process.env.PORT || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

app.prepare().then(() => {
  const mainServer = express()
  const adminServer = express()
  const memberServer = express()

  adminServer.get('/', (req, res) => {
    return app.render(req, res, '/admin', req.query)
  })

  adminServer.get('/*', (req, res) => {
    return app.render(req, res, `/admin${req.path}`, req.query)
  })

  adminServer.all('*', (req, res) => {
    return handle(req, res)
  })

  memberServer.get('/', (req, res) => {
    return app.render(req, res, '/member', req.query)
  })

  memberServer.get('/*', (req, res) => {
    return app.render(req, res, `/member${req.path}`, req.query)
  })

  memberServer.all('*', (req, res) => {
    return handle(req, res)
  })

  mainServer.use(vhost('admin.lvh.me', adminServer))
  mainServer.use(vhost('lvh.me', memberServer))
  mainServer.use(vhost('www.lvh.me', memberServer))
  mainServer.listen(port, (err) => {
    if (err) throw err

    console.log(`> Ready on http://lvh.me:${port}`)
  })
})

See the repo to see this in action.

Repo: https://github.com/dcangulo/nextjs-subdomain-example