Documentation

Path Redirects

(For full domain redirects, check out the Project Settings > Domains page)

You can configure path level redirects via the Servd dashboard ranging from simple, full string matches to complex regex-based rewrites.

Path redirects are configured on a per-environment basis, allowing you to test specific configurations in Staging before copying those same settings over to production.

You can configure path redirects from the [Environment] > Redirects page in the dashboard.

Redirects are evaluated before any other logic within your project. If a redirect matches the incoming request a response will immediately be sent back to the client without PHP or Servd's caching layer coming into play.

Redirects are evaluated in order. The first one in the list which matches the incoming URL pattern will be used and no further redirects will be checked.

Incorrectly formed redirects can take your entire project offline. We have put several safeguards in place to try to prevent this from happening, but you should always test new redirects in staging before applying them to production.

All redirects are formed from three elements:

  1. From: a pattern which is checked against the incoming URL path (the part after the domain, excluding query params). If this pattern matches the redirect takes effect.
  2. To: the resulting location of the redirect. This can optionally contain capture groups collected in the From pattern.
  3. Type: whether the redirect should be a permanent (301), temporary (302) or rewrite (invisible) redirect.

The From pattern is evaluated as regex. The full scope of regex is too large to discuss here, but you can learn more here and try some examples here.

The 'rewrite' Type allows you to create a redirect which does not return to the user, but happens internally within the web server. This allows the same URL to display in their browsers, whilst sending a different path to Craft to process.

Examples #

Here are some common patterns which you might find useful:

Exact match redirect to another URL

From: ^/the-old-path$
To: /the-new-path

Blanket redirect from one domain to another domain

Domain: domain.com
From: /(.*)
To: https://anotherdomain.com/$1

Exact match redirect to another website

From: ^/the-old-path$
To: https://anotherdomain.com/the-old-path

Remove a section from the path for multiple URLs using a capture group

From: ^/news/category/(.*)$
To: /news/$1

Capture multiple segments of a URL

From: ^/blog/([^/]+)/[0-9]{4}/([^/]+)$
To: /$1/$2

The above pattern would match the path /blog/category/2020/blog-post-name and return a redirect to /category/blog-post-name.

Bulk Importing Redirects #

If you have a large number of redirects to add, for example when migrating from an old website, you can import them all at once from a CSV or JSON file instead of entering them one by one.

To import redirects, click the Import button below the redirects list on the [Environment] > Redirects page. You can either upload a .csv or .json file, or paste its contents directly into the text box.

Before anything is saved you'll be shown a summary of the import, including:

  • The number of redirects which will be imported
  • The number of rows which cannot be imported, along with the line number and the reason for each one
  • A sample of the redirects which will be imported

From the summary you can also choose how the imported redirects should be combined with your existing ones:

  • Append: imported redirects are added after your existing redirects
  • Prepend: imported redirects are added before your existing redirects, so they take priority
  • Replace all: all of your existing redirects are removed and replaced with the imported ones

Remember that redirects are evaluated in order, so the position of your imported redirects can change which redirect is used for a given URL.

Only the valid rows will be imported; any invalid rows are skipped. If you have unsaved changes in the redirects list when you import, they will be discarded.

As with any change to redirects, we recommend importing into staging and testing thoroughly before importing into production.

Fields

Each redirect in an import is made up of the following fields:

  • domain (optional): restricts the redirect to requests for a single domain, e.g. example.com. Leave it empty to apply the redirect to all domains. This must be a valid domain name, without https:// or a path.
  • from (required): the regex pattern matched against the incoming URL path, exactly as you would enter it in the dashboard. A value of / is not allowed (see Redirect Loops below).
  • to (required): the destination path or URL. This can use capture groups from the from pattern, e.g. $1. Relative paths without a leading / will have one added automatically.
  • type (optional): one of permanent, redirect (temporary) or rewrite. You can also use 301 for permanent, and 302 or temporary for a temporary redirect. If left empty, the redirect will be permanent. Rewrites cannot point to external URLs.

Each row is checked using the same rules as redirects entered in the dashboard. A row will also be skipped if a redirect with the same domain and from pattern already exists, either in your existing redirects (unless you choose Replace all) or earlier in the same import.

CSV Format

CSV files should contain one redirect per line. The first line can optionally be a header row naming the columns, in which case the columns can be in any order:

domain,from,to,type
,^/the-old-path$,/the-new-path,permanent
,^/news/category/(.*)$,/news/$1,redirect
domain.com,/(.*),https://anotherdomain.com/$1,301
,^/internal-path$,/another-path,rewrite

If there is no header row, the columns must be in the order domain, from, to, type. Leave a column empty (but keep the comma) to skip an optional field. If a value contains a comma, wrap it in double quotes, e.g. "^/a,b$". Blank lines are ignored.

Most spreadsheet applications can export a sheet in this format using a "Save as CSV" or "Download as CSV" option.

JSON Format

JSON imports should be an array of objects, each with domain, from, to and type properties. Optional properties can be left out or set to an empty value:

[
    { "from": "^/the-old-path$", "to": "/the-new-path" },
    { "from": "^/news/category/(.*)$", "to": "/news/$1", "type": "redirect" },
    { "domain": "domain.com", "from": "/(.*)", "to": "https://anotherdomain.com/$1", "type": "permanent" }
]

You can also wrap the array in an object with a redirects property, e.g. { "redirects": [ ... ] }.

Remember that backslashes in regex patterns must be escaped in JSON, so \d should be written as \\d.

Limits

Each import can contain up to 5,000 redirects and be up to 2MB in size. If you need to import more than this, split your redirects into several files and import them one at a time using the Append option.

Common Problems #

Redirect Loops

It is important to consider what will happen once your redirect has taken place and the subsequent request is sent to the server. Will it be matched by the same redirect again? If so it could result in an infinite redirect loop.

A common example is adding a From value of /:
 

From: /
To: /admin

Because we use regex to match the From string, the value of / will match every possible path (they all begin with a /) resulting in an infinite redirect loop. For this reason we explicitly block a From value of / being used. Instead we recommend using ^/$ which will only match the path / exactly, and nothing else.

Similarly, consider the following redirect to add a portion to the URL:
 

From: ^/news/(.*)$
To: /news/archive/$1

The path /news/an-article will match the From pattern and return a redirect to /news/archive/an-article which is exactly what we want, but the end-user's browser will then send another request to the server with this new path.

The new path also matches the From pattern, so the server will respond with another redirect to /news/archive/archive/an-article and this will loop until the end-user's browser gives up trying.

The regex required to avoid these situations can be complex. In this particular circumstance we can use a negative lookahead:

From: ^(?!.*/archive/)/news/(.*)$
To: /news/archive/$1

We'll leave it to you to figure out exactly why that works 🙂