5

I have a monorepo with two projects - web and docs. Each of these is their own Vercel project with the web project mounted at https://example.com and docs mounted at https://docs.example.com. All of this works as expected.

I now want to have the docs project be available at https://example.com/docs. In the web project, I setup the following rewrites in my vercel.json file.

{
  "rewrites": [
    {
      "source": "/docs/:match*",
      "destination": "https://docs.example.com/:match*"
    },
    { "source": "/(.*)", "destination": "/" }
  ]
}

This works for the main index file, but all of the corresponding css and js files result in 404's. The browser is looking for those files at https://example.com/_next which isn't correct, it should be looking at https://docs.example.com/_next.

How do I make this work?

some-user
  • 3,888
  • 5
  • 19
  • 43
Bill
  • 25,119
  • 8
  • 94
  • 125
  • Have you looked into setting up a [`basePath`](https://nextjs.org/docs/api-reference/next.config.js/basepath) in the docs app? – juliomalves Sep 06 '22 at 18:16
  • I can't set both the `web` and `docs` projects to `example.com`. If I use the basePath in the `docs` app, the `docs` app would just be rooted at `docs.example.com/docs` which isn't what I want. – Bill Sep 06 '22 at 19:46
  • 1
    It's bad practice to have the same content at two locations. Choose docs.example.com or example.com/docs as the right location. Set the right basePath for this location. Then use redirect instead of rewrite from the other location to this one. – some-user Sep 11 '22 at 10:18
  • @some-user Yes, that's what I actually want. I can't figure out how to get this to work. I can't set both projects to be hosted at example.com so I set one up to docs.example.com thinking I could redirect it. Can you be more explicit as to what domain each project should use and what the rewrites should be? – Bill Sep 11 '22 at 21:48

3 Answers3

5

rewrite vs. redirect

Let's first understand this difference.

A rewrite is something that happens on the server only. The server will rewrite the URL that has been requested and search for content of that rewritten URL. The client will not know anything about that. E.g. if /a.html is rewritten to /b.html, the client will receive the same content on both URLs. Client won't know, if there is the same file two times or if one (or both) requests have been rewritten to some other resource.

A redirect on the other hand side involves the client (i.e. browser). If the server is asked for an URL that should be redirected, it will reply back to the client/browser with the destination URL of the rewrite. The client will then send another request for the new URL and make this visible to the end user by changing the address in the navigation bar. If /a.html is redirected to /b.html, the client will not receive the actual content of b.html when requesting a.html, but the browser will update the address to b.html and send a new request.

What's the issue with rewrites in your case?

The HTML contains references to other resources using absolute paths, e.g. something like:

<script src="/_next/static/..."></script>

If this file should be served as docs.example.com and example.com/docs (e.g. using rewrites), the actual HTML will not change. The browser will thus try to access docs.example.com/_next/static/... or example.com/_next/static/... respectively. That works for the first case (docs.example.com), but not for the second one. You've noticed that already.

You can change the basePath of next, e.g. to /docs. Then the HTML would contain <script src="/docs/_next/...">. That would make the browser request docs.example.com/docs/_next/... or example.com/docs/_next/... respectively. That would work for the second case, but not the first one. You could heal the first case with more rewrite rules, but I'd suggest a KISS solution.

Now what?

As mentioned in the comments, placing the exact same content at two different addresses is not good practice. And you can see, that is is causing subsequent difficulties as well. (Not to mention punishment by search engines for duplicate content.)

A good solution would be to decide where to store the content. That should be either docs.example.com or example.com/docs, not both.

Using docs.example.com, forwarding example.com/docs/ to docs.example.com

I'd suggest (and assume in this section) to take docs.example.com to have a clear separation of concerns.

So in Vercel you would set up two projects. One for your "main" next instance, another one for the "docs" next instance. (Both can come from the same repo, that doesn't matter.)

You then assign domains to both projects, e.g. www.example.com to the "main" project, docs.example.com to the "docs" project. example.com as well as docs.example.com should be working right now. example.com/docs/ should yield a 404 error.

Then you add redirects (not rewrites!) for your "main" project by adding a vercel.json like this:

{
  "redirects": [
    { "source": "/docs/:path*", "destination": "https://docs.example.com/:path*" }
  ]
}

Now, if you enter example.com/docs/foo in your browser, you should be redirected to docs.example.com/foo and the page should load properly.

Using only example.com/docs/

If you decide to have the docs content only at example.com/docs/, the solution would be as follows:

  • Add basePath: '/docs' to next.config.js of the docs next project. Do not add a domain to this vercel project.
  • In the "main" next project add a vercel.json with a rewrite like this:
{
  "rewrites": [
    { "source": "/docs", "destination": "https://$domain-of-docs-project.vercel.app/docs" },
    { "source": "/docs/:path*", "destination": "https://$domain-of-docs-project.vercel.app/docs/:path*" }
  ]
}

Please comment, if you have additional questions or this doesn't fix the issue.

some-user
  • 3,888
  • 5
  • 19
  • 43
  • Cool, that redirect works. How would I do it if I only want the content at `example.com/docs`? – Bill Sep 12 '22 at 07:29
  • I extended the solution above to cover this case. – some-user Sep 12 '22 at 08:27
  • Thanks. I added the base path to the `docs` project and removed the domain (only the `example-docs.vercel.app` remains. I then added the rewrites to the `vercel.json` file of the `main` project. Now when I go to `example.com/docs` I get **This page isn't working. Too many rewrites.**. – Bill Sep 12 '22 at 16:35
  • If I remove the basepath from the `docs` project and point the destination of the rewrite to `https://example-docs.vercel.app/:path*` then the rewrite works but it is looking for the css and other assets in `https://example.com/_next/static/css` instead of at `https://example-docs.vercel.app/_next_static/css`. – Bill Sep 12 '22 at 16:43
  • 2
    I've added another rewrite rule might solve the redirect issue even though I cannot reproduce it. Please check your vercel/next config for additional redirects that should not be there. – some-user Sep 12 '22 at 18:47
  • Really appreciate the help. This now works for everything but things in the `/public` folder in the docs project. Images and font files are returning 404's but everything else works. It's looking for them at https://example.com/docs/_next/image?url=%2Flogo-dark.png&w=640&q=75. Found this https://github.com/vercel/next.js/issues/17889 but looks like it should have been fixed. – Bill Sep 17 '22 at 08:23
0

I think you can use the vercel.json like this:

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

Next offers a specific example for this.

Your "web" project should handle rewrites, as seen here. next.config.js:

const { DOCS_URL } = process.env //this should be the vercel URL NOT docs.example.com

/** @type {import('next').NextConfig} */
module.exports = {
  async rewrites() {
    return [
      {
        source: '/:path*',
        destination: `/:path*`,
      },
      {
        source: '/docs',
        destination: `${DOCS_URL}/docs`,
      },
      {
        source: '/blog/:path*',
        destination: `${BLOG_URL}/docs/:path*`,
      },
    ]
  },
}

Your "docs" app needs to set a base path, as seen here. next.config.js:

    module.exports = {
      basePath: '/docs',
    }

Then to ensure that docs.example.com no longer serves the docs as well, I'd just remove the DNS record. Instead you'll just have a vercel URL that you will use in the rewrites in "web". No need to point to that server from docs.example.com.

Nextjs documentation for multi-zone

Nextjs example with multi-zones

Tevon Strand-Brown
  • 1,283
  • 3
  • 16
  • 28
  • This works for everything but the `/public` folder in the `docs` project. Images and fonts all return 404s. It's looking for them at https://example.com/docs/_next/image?url=%2Flogo-dark.png&w=640&q=75. Any thoughts on how to fix that? – Bill Sep 17 '22 at 08:24
  • Are these fonts and images coming from the docs app, or the main app? Seems that "baseUrl" may not effect public. try this, add: `{ source: '/docs/_next/:path*', destination: '${BLOG_URL}/_next/:path}` to your next.config.js – Tevon Strand-Brown Sep 18 '22 at 23:51