Static Redirects on Vercel

A redirect is a rule which sends users to a different URL than the one they requested. They are most commonly used to ensure that browsers still get to the correct page after it has been moved to a new URL.

If you have a relatively small number of redirects and don’t need to do anything too fancy then static (or “configuration”) redirects are a good option. Static redirects are configured on Vercel by adding entries to the vercel.json configuration file. There’s just one major snag: you can only create 1024 redirects using this mechanism.

🚀 TL;DR Show me the code. Look at the 21-vercel-redirect-static branch. This site is deployed here.

Static Redirects & Rewrites

Vercel actually provides both redirects and rewrites via vercel.json. They serve similar purposes but are subtly different.

When a redirect rule is matched by a particular URL the response will have a redirect status code and a Location header which points to another URL. In the browser the address bar will update with the new URL. The status code can be one of

  • 301 and 308 — permanent redirect;
  • 302 and 307 — temporary redirect; and
  • 303.

By contrast with a redirect, where the URL is updated in the browser, a rewrite rule will return content from another URL but the URL in the address bar should remain unmodified. Effectively the server is returning content as if it were available at the requested URL.

Redirects

We can set up static redirects by adding a redirects entry to vercel.json. This is analogous to the _redirects file in a Netlify deploy.

{
    "redirects": [
        {
            "source": "/what-is-asciidoc/",
            "destination": "/1.2/what-is-asciidoc/",
            "permanent": false
        },
        {
            "source": "/what-is-gatsby/",
            "destination": "/1.2/what-is-gatsby/",
            "permanent": true
        },
        {
            "source": "/what-is-tailwind/",
            "destination": "/1.2/what-is-tailwind/",
            "statusCode": 301
        },
        {
            "source": "/post/:path(.*)",
            "destination": "/1.2/:path"
        }
    ]
}

That will set up four redirects. For example, /what-is-asciidoc/ will be redirected to /1.2/what-is-asciidoc/. The permanent field can be used to stipulate the redirect status code:

  • "permanent": false — 307 and
  • "permanent": true — 308 (default).

Alternatively you can also use the statusCode field to give an another explicit status code not covered by those two options.

The fourth redirect is an example of using wildcards and REGEX in the source and destination paths. You can also add more sophisticated filters based on specific request headers. See the Vercel documentation for further details.

🚨 The destination must be a valid path and cannot trigger another redirect. There can also be compatability issues between server redirects and client redirects.

What happens when a redirect rule matches?

  1. You request an URL that matches a redirect rule.
  2. The server generates a redirect response which includes
    • the appropriate status code and
    • a Location header with the redirect destination URL.
  3. The redirect response is sent back to the browser.
  4. The browser interprets the status code.
  5. The browser creates and sends a new request for the URL specified in the Location header.
  6. The server responds with the requested content and a 200 status code.
  7. The browser updates the URL bar with the destination URL and the renders the content.

We can see this behaviour by running the following command:

wget -S https://gatsby-whimsyweb-21-vercel-redirect-static.vercel.app/what-is-asciidoc/

That generates the following (somewhat redacted) output:

HTTP request sent, awaiting response... 
  HTTP/1.1 308 Permanent Redirect
  Location: /what-is-asciidoc/
Location: /what-is-asciidoc/ [following]
HTTP request sent, awaiting response... 
  HTTP/1.1 307 Temporary Redirect
  Location: /1.2/what-is-asciidoc/
Location: /1.2/what-is-asciidoc/ [following]
HTTP request sent, awaiting response... 
  HTTP/1.1 200 OK
  Content-Length: 32721
  Content-Type: text/html; charset=utf-8

Compare that with:

wget -S https://gatsby-whimsyweb-21-vercel-redirect-static.vercel.app/what-is-gatsby/
HTTP request sent, awaiting response... 
  HTTP/1.1 308 Permanent Redirect
  Location: /what-is-gatsby/
Location: /what-is-gatsby/ [following]
HTTP request sent, awaiting response... 
  HTTP/1.1 308 Permanent Redirect
  Location: /1.2/what-is-gatsby/
Location: /1.2/what-is-gatsby/ [following]
HTTP request sent, awaiting response... 
  HTTP/1.1 200 OK
  Content-Length: 33028
  Content-Type: text/html; charset=utf-8

In both cases the request initially elicits a 308 response. However, in the first case it is followed by a 307, while in the second case it’s followed by a 308. In both cases the final page is returned with a 200 status code.

What’s the purpose of the initial 308 response? TBH I’m not quite sure. But maybe this is a clue. If we request an URL without a trailing slash then there’s a 308 redirect to the same path with a trailing slash added.

wget -S https://gatsby-whimsyweb-21-vercel-redirect-static.vercel.app/what-is-gatsby   
HTTP request sent, awaiting response...
  HTTP/1.1 308 Permanent Redirect
  Location: /what-is-gatsby
Location: /what-is-gatsby [following]
HTTP request sent, awaiting response... 
  HTTP/1.1 308 Permanent Redirect
  Location: /what-is-gatsby/
Location: /what-is-gatsby/ [following]
HTTP request sent, awaiting response... 
  HTTP/1.1 308 Permanent Redirect
  Location: /1.2/what-is-gatsby/
Location: /1.2/what-is-gatsby/ [following]
HTTP request sent, awaiting response... 
  HTTP/1.1 200 OK
  Content-Length: 33028
  Content-Type: text/html; charset=utf-8

Now let’s test the wildcard redirect.

wget -S https://gatsby-whimsyweb-21-vercel-redirect-static.vercel.app/post/what-is-tailwind/
HTTP request sent, awaiting response... 
  HTTP/1.1 308 Permanent Redirect
  Location: /1.2/what-is-tailwind/
Location: /1.2/what-is-tailwind/ [following]
HTTP request sent, awaiting response... 
  HTTP/1.1 200 OK
  Content-Length: 32823
  Content-Type: text/html; charset=utf-8

That introduces yet another 308 response where the location is updated with a trailing slash. Perhaps that initial 308 is doing some other URL cleanup operation?

⚠️ One significant limitation of static redirects via vercel.json is that you can create only 1024 of them. For many sites this will not be a problem. If, however, you need to apply an enormous steaming pile of redirects then you will need to work a little harder.

Rewrites

Rewrites differ from redirects in that everything happens on the server and is transparent to the client. The server doesn’t send any redirection status codes to the client, just responds with the final result.

{
    "rewrites": [
        {
            "source": "/blog/",
            "destination": "/1.2/"
        },
        {
            "source": "/latest/:path(.*)",
            "destination": "/1.2/:path"
        }
    ]
}

Let’s test the rewrite for /blog/.

wget -S https://gatsby-whimsyweb-21-vercel-redirect-static.vercel.app/blog/   
HTTP request sent, awaiting response... 
  HTTP/1.1 200 OK
  Content-Disposition: inline; filename="1.2"
  Content-Length: 30585
  Content-Type: text/html; charset=utf-8

The server immediately responds with a 200 status code and the content for the /1.2/ path.

What about the wildcard rewrite?

wget -S https://gatsby-whimsyweb-21-vercel-redirect-static.vercel.app/latest/what-is-tailwind/
HTTP request sent, awaiting response... 
  HTTP/1.1 200 OK
  Content-Disposition: inline; filename="what-is-tailwind"
  Content-Length: 32823
  Content-Type: text/html; charset=utf-8

🚨 The destination must be a valid path and cannot trigger another redirect. This had me perplexed for a while!

Conclusion

Redirects enable you to change resource paths but still ensure that requests for the original paths are honoured, modifying the URL in the address bar in the process. Rewrites allow you to do something similar while leaving the URL in the address bar unmodified. Both can be implemented by entries in the vercel.json file, provided that no more than 1024 entries are required.

🚀 TL;DR Show me the code. Look at the 21-vercel-redirect-static branch. This site is deployed here.