1

I want my subdomain sub.example.com to render the page example.com/brand without changing the URL.

Examples

  • sub.example.com/ should render example.com/brand
  • sub.example.com/work should render example.com/brand/work

I tried using the next rewrite in next.config.json

// next.config.ts — initial attempt
async rewrites() {
  return [
    {
      source: "/:path*",
      has: [{ type: "host", value: "sub.example.com" }],
      destination: "/brand/:path*",
    },
  ];
}

When I hit sub.example.com, I expected to see the content on example.com/brand. But it always rendered the home page for example.com.

1 Answer 1

1

Test

For convenience I set up a test env to replicate live.

How to test locally

You can simulate the subdomain on localhost by setting a fake host that points to 127.0.0.1, then run your dev server and visit that fake host. Two ways:

Next.js dev server

  1. Edit your hosts file
  • macOS or Linux - open /etc/hosts with sudo and add to the bottom of the file:
127.0.0.1 example.test
127.0.0.1 sub.example.test

Windows - open C:\Windows\System32\drivers\etc\hosts as Administrator and add the same two lines.

  1. Start your app locally npm run dev Next.js usually runs on http://localhost:3000.
  2. You can visit the fake host in your browser http://example.test:3000 or http://sub.example.test:3000

Cause

When rewrites() returns an array, on default the rules run after filesystem routes loads. Therefore, root files comes before rewrite rules and overwrites it. If a real route exists at / for example app/page.tsx or pages/index.tsx, it matches before the rewrite. Therefore if I had tried rewriting something like this {source: "/", destination: "/brand"} visiting example.com would display the normal file (root) page example.com. But {source: "/probe", destination: "/brand"}(/probe route doesn't exist on my route pages) if I visit example.com/probe it would render example.com/probe but it would show me the content of example.com/brand (good).

Minimal local sanity check

Add a throwaway rule to verify rewriting works without host conditions:

async rewrites() {
  return { afterFiles: [{ source: "/probe", destination: "/brand" }] };
}

Visit http://localhost:3000/probe or http://example.test:3000/probe and you should see /brand. Then add the beforeFiles root rule and the host based catch all as shown above.

Solution

The solution is to use the beforeFiles() to rewrite the root route; it return an object from rewrites() and put the root rule in beforeFiles so it runs before filesystem routes. Also keep a catch all in afterFiles for subpaths. Read

// next.config.ts — final working config
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  async rewrites() {
    return {
      // 1) Root of sub.example.com goes to /brand
      beforeFiles: [
        {
          source: "/",
          has: [{ type: "host", value: "sub.example.com" }],
          destination: "/brand",
        },
      ],
      // 2) Subpaths on sub.example.com map to /brand/*
      afterFiles: [
        {
          source: "/:path*",
          has: [{ type: "host", value: "sub.example.com" }],
          destination: "/brand/:path*",
        },
      ],
    };
  },
};

export default nextConfig;

Notes

  • Path case matters on Linux. If your folder is app/Brand, use /Brand, not /brand.
  • Rewrites keep the URL only if the upstream does not redirect. If you rewrite to an origin that issues a 301 or 302 the browser URL will change.
  • You can also do similar with vercel.json config but I could not figure how to make vercel rewite for the host, but it worked for any other path.

Vercel case

This works as normal

{
  "rewrites": [
    {
      "source": "/probe",
      "destination": "/brand"
    }
  ]
}

This I haven't figure out yet. But it works for anyother route that isn't the root. But I doubt that is good enought for you

{
  "rewrites": [
    { "source": "/",      "has": [{ "type": "host", "value": "sub.example.com" }], "destination": "/brand" },
    { "source": "/:path*", "has": [{ "type": "host", "value": "sub.example.com" }], "destination": "/brand/:path*" }
  ]
}

References

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.