diff --git a/app/core/layouts/ContentsLayout.js b/app/core/layouts/ContentsLayout.js index b9035fad..a2dc3d90 100644 --- a/app/core/layouts/ContentsLayout.js +++ b/app/core/layouts/ContentsLayout.js @@ -176,7 +176,7 @@ export function ContentsLayout({children, meta, tableOfContents: toc}) { The exported `pathname` can also be a filename (for example, -> `/readme.md`), but you may need to set the `Content-Type` header to -> `text/html` when serving its content if it is different than `.html`. - -#### Example - -Original pages: - -- `pages/index.js` -- `pages/about.js` -- `pages/post.js` - -`blitz.config.js`: - -```js -module.exports = { - exportPathMap: async function ( - defaultPathMap, - {dev, dir, outDir, distDir, buildId}, - ) { - return { - "/": {page: "/"}, - "/about": {page: "/about"}, - "/p/hello-nextjs": {page: "/post", query: {title: "hello-nextjs"}}, - "/p/learn-nextjs": {page: "/post", query: {title: "learn-nextjs"}}, - "/p/deploy-nextjs": { - page: "/post", - query: {title: "deploy-nextjs"}, - }, - } - }, -} -``` - ## Trailing Slash {#trailing-slash} By default Blitz will redirect urls with trailing slashes to their diff --git a/app/pages/docs/code-splitting.mdx b/app/pages/docs/code-splitting.mdx index e5dea783..7bc5abf9 100644 --- a/app/pages/docs/code-splitting.mdx +++ b/app/pages/docs/code-splitting.mdx @@ -55,6 +55,14 @@ export default Home `../components/hello`. It works like a regular React Component, and you can pass props to it as you normally would. +> **Note**: In `import('path/to/component')`, the path must be explicitly +> written. It can't be a template string nor a variable. Furthermore the +> `import()` has to be inside the `dynamic()` call for Next.js to be able +> to match webpack bundles / module ids to the specific `dynamic()` call +> and preload them before rendering. `dynamic()` can't be used inside of +> React rendering as it needs to be marked in the top level of the module +> for preloading to work, similar to `React.lazy`. + ## With named exports {#with-named-exports} If the dynamic component is not the default export, you can use a named diff --git a/app/pages/docs/headers.mdx b/app/pages/docs/headers.mdx new file mode 100644 index 00000000..c9812abb --- /dev/null +++ b/app/pages/docs/headers.mdx @@ -0,0 +1,390 @@ +--- +title: HTTP Headers +sidebar_label: HTTP Headers +--- + +Headers allow you to set custom HTTP headers for an incoming request path. + +To set custom HTTP headers you can use the `headers` key in +`blitz.config.js`: + +```js +module.exports = { + async headers() { + return [ + { + source: "/about", + headers: [ + { + key: "x-custom-header", + value: "my custom header value", + }, + { + key: "x-another-custom-header", + value: "my other custom header value", + }, + ], + }, + ] + }, +} +``` + +`headers` is an async function that expects an array to be returned +holding objects with `source` and `headers` properties: + +- `source` is the incoming request path pattern. +- `headers` is an array of header objects with the `key` and `value` + properties. +- `basePath`: `false` or `undefined` - if false the basePath won't be + included when matching, can be used for external rewrites only. +- `locale`: `false` or `undefined` - whether the locale should not be + included when matching. +- `has` is an array of [has objects](#header-cookie-and-query-matching) + with the `type`, `key` and `value` properties. + +Headers are checked before the filesystem which includes pages and +`/public` files. + +## Header Overriding Behavior {#overriding} + +If two headers match the same path and set the same header key, the last +header key will override the first. Using the below headers, the path +`/hello` will result in the header `x-hello` being `world` due to the last +header value set being `world`. + +```js +module.exports = { + async headers() { + return [ + { + source: '/:path*', + headers: [ + { + key: 'x-hello', + value: 'there', + }, + ], + }, + { + source: '/hello', + headers: [ + { + key: 'x-hello', + value: 'world', + }, + ], + }, + ], + }, +} +``` + +## Path Matching {#path-matching} + +Path matches are allowed, for example `/blog/:slug` will match +`/blog/hello-world` (no nested paths): + +```js +module.exports = { + async headers() { + return [ + { + source: '/blog/:slug', + headers: [ + { + key: 'x-slug', + value: ':slug', // Matched parameters can be used in the value + }, + { + key: 'x-slug-:slug', // Matched parameters can be used in the key + value: 'my other custom header value', + }, + ], + }, + ], + }, +} +``` + +### Wildcard Path Matching {#wildcard} + +To match a wildcard path you can use `*` after a parameter, for example +`/blog/:slug*` will match `/blog/a/b/c/d/hello-world`: + +```js +module.exports = { + async headers() { + return [ + { + source: '/blog/:slug*', + headers: [ + { + key: 'x-slug', + value: ':slug*', // Matched parameters can be used in the value + }, + { + key: 'x-slug-:slug*', // Matched parameters can be used in the key + value: 'my other custom header value', + }, + ], + }, + ], + }, +} +``` + +### Regex Path Matching {#regex} + +To match a regex path you can wrap the regex in parenthesis after a +parameter, for example `/blog/:slug(\\d{1,})` will match `/blog/123` but +not `/blog/abc`: + +```js +module.exports = { + async headers() { + return [ + { + source: '/blog/:post(\\d{1,})', + headers: [ + { + key: 'x-post', + value: ':post', + }, + ], + }, + ], + }, +} +``` + +The following characters `(`, `)`, `{`, `}`, `:`, `*`, `+`, `?` are used +for regex path matching, so when used in the `source` as non-special +values they must be escaped by adding `\\` before them: + +```js +module.exports = { + async redirects() { + return [ + { + // this will match `/english(default)/something` being requested + source: "/english\\(default\\)/:slug", + destination: "/en-us/:slug", + permanent: false, + }, + ] + }, +} +``` + +## Header, Cookie, and Query Matching {#header-cookie-matching} + +To only apply a header when either header, cookie, or query values also +match the `has` field can be used. Both the `source` and all `has` items +must match for the header to be applied. + +`has` items have the following fields: + +- `type`: `String` - must be either `header`, `cookie`, `host`, or + `query`. +- `key`: `String` - the key from the selected type to match against. +- `value`: `String` or `undefined` - the value to check for, if undefined + any value will match. A regex like string can be used to capture a + specific part of the value, e.g. if the value `first-(?.*)` + is used for `first-second` then `second` will be usable in the + destination with `:paramName`. + +```js +module.exports = { + async headers() { + return [ + // if the header `x-add-header` is present, + // the `x-another-header` header will be applied + { + source: "/:path*", + has: [ + { + type: "header", + key: "x-add-header", + }, + ], + headers: [ + { + key: "x-another-header", + value: "hello", + }, + ], + }, + // if the source, query, and cookie are matched, + // the `x-authorized` header will be applied + { + source: "/specific/:path*", + has: [ + { + type: "query", + key: "page", + // the page value will not be available in the + // header key/values since value is provided and + // doesn't use a named capture group e.g. (?home) + value: "home", + }, + { + type: "cookie", + key: "authorized", + value: "true", + }, + ], + headers: [ + { + key: "x-authorized", + value: ":authorized", + }, + ], + }, + // if the header `x-authorized` is present and + // contains a matching value, the `x-another-header` will be applied + { + source: "/:path*", + has: [ + { + type: "header", + key: "x-authorized", + value: "(?yes|true)", + }, + ], + headers: [ + { + key: "x-another-header", + value: ":authorized", + }, + ], + }, + // if the host is `example.com`, + // this header will be applied + { + source: "/:path*", + has: [ + { + type: "host", + value: "example.com", + }, + ], + headers: [ + { + key: "x-another-header", + value: ":authorized", + }, + ], + }, + ] + }, +} +``` + +### Headers with basePath support {#basepath} + +When leveraging [`basePath` support](./blitz-config#base-path) with +headers each `source` is automatically prefixed with the `basePath` unless +you add `basePath: false` to the header: + +```js +module.exports = { + basePath: "/docs", + + async headers() { + return [ + { + source: "/with-basePath", // becomes /docs/with-basePath + headers: [ + { + key: "x-hello", + value: "world", + }, + ], + }, + { + source: "/without-basePath", // is not modified since basePath: false is set + headers: [ + { + key: "x-hello", + value: "world", + }, + ], + basePath: false, + }, + ] + }, +} +``` + +### Headers with i18n support {#i18n} + +When leveraging [`i18n` support](./i18n-routing) with headers each +`source` is automatically prefixed to handle the configured `locales` +unless you add `locale: false` to the header. If `locale: false` is used +you must prefix the `source` with a locale for it to be matched correctly. + +```js +module.exports = { + i18n: { + locales: ["en", "fr", "de"], + defaultLocale: "en", + }, + + async headers() { + return [ + { + source: "/with-locale", // automatically handles all locales + headers: [ + { + key: "x-hello", + value: "world", + }, + ], + }, + { + // does not handle locales automatically since locale: false is set + source: "/nl/with-locale-manual", + locale: false, + headers: [ + { + key: "x-hello", + value: "world", + }, + ], + }, + { + // this matches '/' since `en` is the defaultLocale + source: "/en", + locale: false, + headers: [ + { + key: "x-hello", + value: "world", + }, + ], + }, + { + // this gets converted to /(en|fr|de)/(.*) so will not match the top-level + // `/` or `/fr` routes like /:path* would + source: "/(.*)", + headers: [ + { + key: "x-hello", + value: "worlld", + }, + ], + }, + ] + }, +} +``` + +### Cache-Control {#cache-control} + +Cache-Control headers set in blitz.config.js will be overwritten in +production to ensure that static assets can be cached effectively. If you +need to revalidate the cache of a page that has been +[statically generated](./pages#automatic-static-optimization), you can do +so by setting `revalidate` in the page's +[`getStaticProps`](./get-static-props) function. diff --git a/app/pages/docs/i18n-routing.mdx b/app/pages/docs/i18n-routing.mdx index 4af6f30b..4d3065c3 100644 --- a/app/pages/docs/i18n-routing.mdx +++ b/app/pages/docs/i18n-routing.mdx @@ -9,8 +9,13 @@ routing. You can provide a list of locales, the default locale, and domain-specific locales and Blitz will automatically handle the routing. The i18n routing support is currently meant to complement existing i18n -library solutions like `react-intl`, `react-i18next`, `lingui`, `rosetta`, -and others by streamlining the routes and locale parsing. +library solutions like +[`react-intl`](https://formatjs.io/docs/getting-started/installation), +[`react-i18next`](https://react.i18next.com/), +[`lingui`](https://lingui.js.org/), +[`rosetta`](https://github.com/lukeed/rosetta), +[`next-intl`](https://github.com/amannn/next-intl) and others by +streamlining the routes and locale parsing. ## Getting started {#getting-started} diff --git a/app/pages/docs/link.mdx b/app/pages/docs/link.mdx index 53445551..841c1369 100644 --- a/app/pages/docs/link.mdx +++ b/app/pages/docs/link.mdx @@ -48,8 +48,12 @@ you can link to it with `Routes.About`. For more information, see the `false` - `prefetch` - Prefetch the page in the background. Defaults to `true`. Any `` that is in the viewport (initially or through scroll) - will be preloaded. Pages using [Static Generation](./get-static-props) - will preload `JSON` files with the data for faster page transitions. + will be preloaded. Prefetch can be disabled by passing + `prefetch={false}`. When `prefetch` is set to `false`, prefetching will + still occur on hover. Pages using + [Static Generation](./get-static-props.mdx) will preload `JSON` files + with the data for faster page transitions. Prefetching is only enabled + in production. - [`replace`](#replace-the-url-instead-of-push) - Replace the current `history` state instead of adding a new url into the stack. Defaults to `false` diff --git a/app/pages/docs/preview-mode.mdx b/app/pages/docs/preview-mode.mdx index d235e6d6..e25d2c48 100644 --- a/app/pages/docs/preview-mode.mdx +++ b/app/pages/docs/preview-mode.mdx @@ -244,8 +244,24 @@ there’s a size limitation. Currently, preview data is limited to 2KB. The preview mode works on `getServerSideProps` as well. It will also be available on the `context` object containing `preview` and `previewData`. +### Works with API Routes {#works-with-api-routes} + +API Routes will have access to `preview` and `previewData` under the +request object. For example: + +```js +export default function myApiRoute(req, res) { + const isPreview = req.preview + const previewData = req.previewData + // ... +} +``` + ### Unique per `blitz build` {#unique-per-blitz-build} The bypass cookie value and private key for encrypting the `previewData` changes when a `blitz build` is ran, this ensures that the bypass cookie can’t be guessed. + +> **Note:** To test Preview Mode locally over HTTP your browser will need +> to allow third-party cookies and local storage access. diff --git a/app/pages/docs/redirects.mdx b/app/pages/docs/redirects.mdx index 85f3c134..98a86509 100644 --- a/app/pages/docs/redirects.mdx +++ b/app/pages/docs/redirects.mdx @@ -3,6 +3,12 @@ title: Redirects sidebar_label: Redirects --- +Redirects allow you to redirect an incoming request path to a different +destination path. + +Redirects are only available on the Node.js environment and do not affect +client-side routing. + ## On the Client {#on-the-client} One common use case is conditionally redirecting a user to a different @@ -50,3 +56,289 @@ export const getServerSideProps = async ({req, res}) => { return {props: {}} } ``` + +## Global Redirects {#global} + +To use global redirects, you can use the `redirects` key in +`blitz.config.js`: + +```js +module.exports = { + async redirects() { + return [ + { + source: "/about", + destination: "/", + permanent: true, + }, + ] + }, +} +``` + +`redirects` is an async function that expects an array to be returned +holding objects with `source`, `destination`, and `permanent` properties: + +- `source` is the incoming request path pattern. +- `destination` is the path you want to route to. +- `permanent` if the redirect is permanent or not. +- `basePath`: `false` or `undefined` - if false the basePath won't be + included when matching, can be used for external rewrites only. +- `locale`: `false` or `undefined` - whether the locale should not be + included when matching. +- `has` is an array of [has objects](#header-cookie-and-query-matching) + with the `type`, `key` and `value` properties. + +Redirects are checked before the filesystem which includes pages and +`/public` files. + +### Path Matching {#path-matching} + +Path matches are allowed, for example `/old-blog/:slug` will match +`/old-blog/hello-world` (no nested paths): + +```js +module.exports = { + async redirects() { + return [ + { + source: "/old-blog/:slug", + destination: "/news/:slug", // Matched parameters can be used in the destination + permanent: true, + }, + ] + }, +} +``` + +#### Wildcard Path Matching + +To match a wildcard path you can use `*` after a parameter, for example +`/blog/:slug*` will match `/blog/a/b/c/d/hello-world`: + +```js +module.exports = { + async redirects() { + return [ + { + source: "/blog/:slug*", + destination: "/news/:slug*", // Matched parameters can be used in the destination + permanent: true, + }, + ] + }, +} +``` + +#### Regex Path Matching + +To match a regex path you can wrap the regex in parentheses after a +parameter, for example `/post/:slug(\\d{1,})` will match `/post/123` but +not `/post/abc`: + +```js +module.exports = { + async redirects() { + return [ + { + source: "/post/:slug(\\d{1,})", + destination: "/news/:slug", // Matched parameters can be used in the destination + permanent: false, + }, + ] + }, +} +``` + +The following characters `(`, `)`, `{`, `}`, `:`, `*`, `+`, `?` are used +for regex path matching, so when used in the `source` as non-special +values they must be escaped by adding `\\` before them: + +```js +module.exports = { + async redirects() { + return [ + { + // this will match `/english(default)/something` being requested + source: "/english\\(default\\)/:slug", + destination: "/en-us/:slug", + permanent: false, + }, + ] + }, +} +``` + +### Header, Cookie, and Query Matching {#header-cooking-matching} + +To only match a redirect when header, cookie, or query values also match +the `has` field can be used. Both the `source` and all `has` items must +match for the redirect to be applied. + +`has` items have the following fields: + +- `type`: `String` - must be either `header`, `cookie`, `host`, or + `query`. +- `key`: `String` - the key from the selected type to match against. +- `value`: `String` or `undefined` - the value to check for, if undefined + any value will match. A regex like string can be used to capture a + specific part of the value, e.g. if the value `first-(?.*)` + is used for `first-second` then `second` will be usable in the + destination with `:paramName`. + +```js +module.exports = { + async redirects() { + return [ + // if the header `x-redirect-me` is present, + // this redirect will be applied + { + source: "/:path*", + has: [ + { + type: "header", + key: "x-redirect-me", + }, + ], + permanent: false, + destination: "/another-page", + }, + // if the source, query, and cookie are matched, + // this redirect will be applied + { + source: "/specific/:path*", + has: [ + { + type: "query", + key: "page", + // the page value will not be available in the + // destination since value is provided and doesn't + // use a named capture group e.g. (?home) + value: "home", + }, + { + type: "cookie", + key: "authorized", + value: "true", + }, + ], + permanent: false, + destination: "/:path*/:page", + }, + // if the header `x-authorized` is present and + // contains a matching value, this redirect will be applied + { + source: "/:path*", + has: [ + { + type: "header", + key: "x-authorized", + value: "(?yes|true)", + }, + ], + permanent: false, + destination: "/home?authorized=:authorized", + }, + // if the host is `example.com`, + // this redirect will be applied + { + source: "/:path*", + has: [ + { + type: "host", + value: "example.com", + }, + ], + destination: "/another-page", + }, + ] + }, +} +``` + +#### Redirects with basePath support + +When leveraging [`basePath` support](./blitz-config#base-path) with +redirects each `source` and `destination` is automatically prefixed with +the `basePath` unless you add `basePath: false` to the redirect: + +```js +module.exports = { + basePath: "/docs", + + async redirects() { + return [ + { + source: "/with-basePath", // automatically becomes /docs/with-basePath + destination: "/another", // automatically becomes /docs/another + permanent: false, + }, + { + // does not add /docs since basePath: false is set + source: "/without-basePath", + destination: "/another", + basePath: false, + permanent: false, + }, + ] + }, +} +``` + +#### Redirects with i18n support + +When leveraging [`i18n` support](./i18n-routing) with redirects each +`source` and `destination` is automatically prefixed to handle the +configured `locales` unless you add `locale: false` to the redirect. If +`locale: false` is used you must prefix the `source` and `destination` +with a locale for it to be matched correctly. + +```js +module.exports = { + i18n: { + locales: ["en", "fr", "de"], + defaultLocale: "en", + }, + + async redirects() { + return [ + { + source: "/with-locale", // automatically handles all locales + destination: "/another", // automatically passes the locale on + permanent: false, + }, + { + // does not handle locales automatically since locale: false is set + source: "/nl/with-locale-manual", + destination: "/nl/another", + locale: false, + permanent: false, + }, + { + // this matches '/' since `en` is the defaultLocale + source: "/en", + destination: "/en/another", + locale: false, + permanent: false, + }, + { + // this gets converted to /(en|fr|de)/(.*) so will not match the top-level + // `/` or `/fr` routes like /:path* would + source: "/(.*)", + destination: "/another", + permanent: false, + }, + ] + }, +} +``` + +In some rare cases, you might need to assign a custom status code for +older HTTP Clients to properly redirect. In these cases, you can use the +`statusCode` property instead of the `permanent` property, but not both. +Note: to ensure IE11 compatibility a `Refresh` header is automatically +added for the 308 status code. + +### Other Redirects {#other} + +- Inside [API Routes](./api-routes), you can use `res.redirect()`. diff --git a/app/pages/docs/rewrites.mdx b/app/pages/docs/rewrites.mdx new file mode 100644 index 00000000..07726f9d --- /dev/null +++ b/app/pages/docs/rewrites.mdx @@ -0,0 +1,400 @@ +--- +title: Rewrites +sidebar_label: Rewrites +--- + +Rewrites allow you to map an incoming request path to a different +destination path. + +Rewrites are only available on the Node.js environment and do not affect +client-side routing. + +To use rewrites you can use the `rewrites` key in `blitz.config.js`: + +```js +module.exports = { + async rewrites() { + return [ + { + source: "/about", + destination: "/", + }, + ] + }, +} +``` + +`rewrites` is an async function that expects an array to be returned +holding objects with `source` and `destination` properties: + +- `source`: `String` - is the incoming request path pattern. +- `destination`: `String` is the path you want to route to. +- `basePath`: `false` or `undefined` - if false the basePath won't be + included when matching, can be used for external rewrites only. +- `locale`: `false` or `undefined` - whether the locale should not be + included when matching. +- `has` is an array of [has objects](#header-cookie-and-query-matching) + with the `type`, `key` and `value` properties. + +Rewrites are applied after checking the filesystem (pages and `/public` +files) and before dynamic routes by default. + +```js +module.exports = { + async rewrites() { + return { + beforeFiles: [ + // These rewrites are checked after headers/redirects + // and before pages/public files which allows overriding + // page files + { + source: "/some-page", + destination: "/somewhere-else", + has: [{type: "query", key: "overrideMe"}], + }, + ], + afterFiles: [ + // These rewrites are checked after pages/public files + // are checked but before dynamic routes + { + source: "/non-existent", + destination: "/somewhere-else", + }, + ], + fallback: [ + // These rewrites are checked after both pages/public files + // and dynamic routes are checked + { + source: "/:path*", + destination: "https://my-old-site.com", + }, + ], + } + }, +} +``` + +## Rewrite parameters {#parameters} + +When using parameters in a rewrite the parameters will be passed in the +query by default when none of the parameters are used in the +`destination`. + +```js +module.exports = { + async rewrites() { + return [ + { + source: "/old-about/:path*", + destination: "/about", // The :path parameter isn't used here so will be automatically passed in the query + }, + ] + }, +} +``` + +If a parameter is used in the destination none of the parameters will be +automatically passed in the query. + +```js +module.exports = { + async rewrites() { + return [ + { + source: "/docs/:path*", + destination: "/:path*", // The :path parameter is used here so will not be automatically passed in the query + }, + ] + }, +} +``` + +You can still pass the parameters manually in the query if one is already +used in the destination by specifying the query in the `destination`. + +```js +module.exports = { + async rewrites() { + return [ + { + source: "/:first/:second", + destination: "/:first?second=:second", + // Since the :first parameter is used in the destination the :second parameter + // will not automatically be added in the query although we can manually add it + // as shown above + }, + ] + }, +} +``` + +## Path Matching {#path-matching} + +Path matches are allowed, for example `/blog/:slug` will match +`/blog/hello-world` (no nested paths): + +```js +module.exports = { + async rewrites() { + return [ + { + source: "/blog/:slug", + destination: "/news/:slug", // Matched parameters can be used in the destination + }, + ] + }, +} +``` + +### Wildcard Path Matching {#wildcard} + +To match a wildcard path you can use `*` after a parameter, for example +`/blog/:slug*` will match `/blog/a/b/c/d/hello-world`: + +```js +module.exports = { + async rewrites() { + return [ + { + source: "/blog/:slug*", + destination: "/news/:slug*", // Matched parameters can be used in the destination + }, + ] + }, +} +``` + +### Regex Path Matching {#regex} + +To match a regex path you can wrap the regex in parenthesis after a +parameter, for example `/blog/:slug(\\d{1,})` will match `/blog/123` but +not `/blog/abc`: + +```js +module.exports = { + async rewrites() { + return [ + { + source: "/old-blog/:post(\\d{1,})", + destination: "/blog/:post", // Matched parameters can be used in the destination + }, + ] + }, +} +``` + +The following characters `(`, `)`, `{`, `}`, `:`, `*`, `+`, `?` are used +for regex path matching, so when used in the `source` as non-special +values they must be escaped by adding `\\` before them: + +```js +module.exports = { + async redirects() { + return [ + { + // this will match `/english(default)/something` being requested + source: "/english\\(default\\)/:slug", + destination: "/en-us/:slug", + permanent: false, + }, + ] + }, +} +``` + +## Header, Cookie, and Query Matching {#header-cookie-matching} + +To only match a rewrite when header, cookie, or query values also match +the `has` field can be used. Both the `source` and all `has` items must +match for the rewrite to be applied. + +`has` items have the following fields: + +- `type`: `String` - must be either `header`, `cookie`, `host`, or + `query`. +- `key`: `String` - the key from the selected type to match against. +- `value`: `String` or `undefined` - the value to check for, if undefined + any value will match. A regex like string can be used to capture a + specific part of the value, e.g. if the value `first-(?.*)` + is used for `first-second` then `second` will be usable in the + destination with `:paramName`. + +```js +module.exports = { + async rewrites() { + return [ + // if the header `x-rewrite-me` is present, + // this rewrite will be applied + { + source: "/:path*", + has: [ + { + type: "header", + key: "x-rewrite-me", + }, + ], + destination: "/another-page", + }, + // if the source, query, and cookie are matched, + // this rewrite will be applied + { + source: "/specific/:path*", + has: [ + { + type: "query", + key: "page", + // the page value will not be available in the + // destination since value is provided and doesn't + // use a named capture group e.g. (?home) + value: "home", + }, + { + type: "cookie", + key: "authorized", + value: "true", + }, + ], + destination: "/:path*/home", + }, + // if the header `x-authorized` is present and + // contains a matching value, this rewrite will be applied + { + source: "/:path*", + has: [ + { + type: "header", + key: "x-authorized", + value: "(?yes|true)", + }, + ], + destination: "/home?authorized=:authorized", + }, + // if the host is `example.com`, + // this rewrite will be applied + { + source: "/:path*", + has: [ + { + type: "host", + value: "example.com", + }, + ], + destination: "/another-page", + }, + ] + }, +} +``` + +## Rewriting to an external URL {#external} + +Rewrites allow you to rewrite to an external url. This is especially +useful for incrementally adopting Blitz.js + +```js +module.exports = { + async rewrites() { + return [ + { + source: "/blog/:slug", + destination: "https://example.com/blog/:slug", // Matched parameters can be used in the destination + }, + ] + }, +} +``` + +### Incremental adoption of Blitz.js {#incremental} + +You can also have Blitz.js fall back to proxying to an existing website +after checking all Blitz.js routes. + +This way you don't have to change the rewrites configuration when +migrating more pages to Blitz.js + +```js +module.exports = { + async rewrites() { + return { + fallback: [ + { + source: "/:path*", + destination: `https://custom-routes-proxying-endpoint.vercel.app/:path*`, + }, + ], + } + }, +} +``` + +### Rewrites with basePath support {#basepath} + +When leveraging [`basePath` support](./blitz-config#base-path) with +rewrites each `source` and `destination` is automatically prefixed with +the `basePath` unless you add `basePath: false` to the rewrite: + +```js +module.exports = { + basePath: "/docs", + + async rewrites() { + return [ + { + source: "/with-basePath", // automatically becomes /docs/with-basePath + destination: "/another", // automatically becomes /docs/another + }, + { + // does not add /docs to /without-basePath since basePath: false is set + // Note: this can not be used for internal rewrites e.g. `destination: '/another'` + source: "/without-basePath", + destination: "https://example.com", + basePath: false, + }, + ] + }, +} +``` + +### Rewrites with i18n support {#18n} + +When leveraging [`i18n` support](./i18n-routing) with rewrites each +`source` and `destination` is automatically prefixed to handle the +configured `locales` unless you add `locale: false` to the rewrite. If +`locale: false` is used you must prefix the `source` and `destination` +with a locale for it to be matched correctly. + +```js +module.exports = { + i18n: { + locales: ["en", "fr", "de"], + defaultLocale: "en", + }, + + async rewrites() { + return [ + { + source: "/with-locale", // automatically handles all locales + destination: "/another", // automatically passes the locale on + }, + { + // does not handle locales automatically since locale: false is set + source: "/nl/with-locale-manual", + destination: "/nl/another", + locale: false, + }, + { + // this matches '/' since `en` is the defaultLocale + source: "/en", + destination: "/en/another", + locale: false, + }, + { + // this gets converted to /(en|fr|de)/(.*) so will not match the top-level + // `/` or `/fr` routes like /:path* would + source: "/(.*)", + destination: "/another", + }, + ] + }, +} +```