The Uniform redirect SDK

The Uniform SDK lets developers work with redirects.

The following example is a platform-agnostic approach to executing on redirects using the Uniform redirect SDK.

import { RedirectClient, WithMemoryCache } from '@uniformdev/redirect' import * as dotenv from 'dotenv' dotenv.config() const client = new RedirectClient({ apiKey: process.env.UNIFORM_API_KEY, projectId: process.env.UNIFORM_PROJECT_ID, // Store a cache of the data needed to resolve redirects in memory. This could potentially get large. // Can operate without it, but will request data every hit. Also of note the API is cached by fastly, so the repeated request response will be fast. // Memory cache only makes sense in middleware environments that retain objects between requests somehow. dataCache: new WithMemoryCache({ prePopulate: true, refreshRate: 20000 }), }) // Expected that whatever product is being used has the ability get get the current URL and perform a redirect. export default async function middleware(request: { getUrl: () => string, redirect: (target: string, statusCode: number) => void, keepGoingNormally: () => void, }) { const url = request.getUrl() const result = await client.processUrlBestMatch(url) if (result) { request.redirect( result.url, result.definition?.redirect.targetStatusCode ?? 301 ) return } request.keepGoingNormally() }

The following examples represent converting Uniform redirects into a format that's executable by third-party hosting tools such as Netlify and Cloudflare:

import { RedirectClient } from '@uniformdev/redirect' import * as dotenv from 'dotenv' import * as fs from 'fs' const convertUniformToNextjs = async () => { dotenv.config() const client = new RedirectClient({ apiKey: process.env.UNIFORM_API_KEY, projectId: process.env.UNIFORM_PROJECT_ID, }) const ret = [] let redirects = (await client.getRedirects({ limit: 50, offset: 0 })) .redirects let count = 0 while (redirects.length) { const redirect = redirects.pop() ret.push({ source: redirect?.redirect.sourceUrl, destination: redirect?.redirect.targetUrl, permanent: redirect?.redirect.targetStatusCode === 301, }) if (!redirects.length) { count++ redirects = (await client.getRedirects({ limit: 50, offset: count * 50 })) .redirects } } if (!fs.existsSync('out')) { fs.mkdirSync('out') } fs.writeFile( 'out/nextJsRedirects.json', JSON.stringify(ret, undefined, ' '), (e) => { if (e) { console.log(e) } } ) } convertUniformToNextjs()

The previous examples of converting Uniform redirects result in an output like the following:

[ { "source": "/a", "destination": "/b", "permanent": true }, { "source": "/this", "destination": "/that", "permanent": true }, { "source": "/product/electronics/walkie-talkie", "destination": "/electronics/walkie-talkie", "permanent": true }, { "source": "/source/path", "destination": "/target/url", "permanent": true } ]
import { RedirectClient } from '@uniformdev/redirect' import * as dotenv from 'dotenv' import * as fs from 'fs' const convertUniformToNetlifyToml = async () => { dotenv.config() const client = new RedirectClient({ apiKey: process.env.UNIFORM_API_KEY, projectId: process.env.UNIFORM_PROJECT_ID, }) const ret: string[][] = [] let redirects = (await client.getRedirects({ limit: 50, offset: 0 })) .redirects let count = 0 while (redirects.length) { const redirect = redirects.pop() ret.push([ '[[redirects]]', `from = "${redirect?.redirect.sourceUrl}"`, `to = "${redirect?.redirect.targetUrl}"`, `status = ${redirect?.redirect.targetStatusCode}`, ]) if (!redirects.length) { count++ redirects = (await client.getRedirects({ limit: 50, offset: count * 50 })) .redirects } } if (!fs.existsSync('out')) { fs.mkdirSync('out') } fs.writeFile( 'out/netlifyTomlRedirects.toml', ret.reduce((cur, o) => { return `${cur}${o.join('\n\t')}\n\n` }, ''), (e) => { if (e) { console.log(e) } } ) } convertUniformToNetlifyToml()

This results in an output, such as the following, to be added to your TOML file:

[[redirects]] from = "/a" to = "/b" status = 301 [[redirects]] from = "/this" to = "/that" status = 301 [[redirects]] from = "/product/electronics/walkie-talkie" to = "/electronics/walkie-talkie" status = 301 [[redirects]] from = "/source/path" to = "/target/url" status = 301
import { RedirectClient } from '@uniformdev/redirect' import * as dotenv from 'dotenv' import * as fs from 'fs' const convertUniformToNetlify_redirects = async () => { const pad = (maxLen: number, curlen: number) => { const ret: string[] = [] for (let i = curlen; i < maxLen; i++) { ret.push(' ') } return ret.join('') } dotenv.config() const client = new RedirectClient({ apiKey: process.env.UNIFORM_API_KEY, projectId: process.env.UNIFORM_PROJECT_ID, }) const ret: { source: string, target: string, statusCode: number }[] = [] let redirects = (await client.getRedirects({ limit: 50, offset: 0 })) .redirects let count = 0 let slen = 0 let tlen = 0 while (redirects.length) { const redirect = redirects.pop()?.redirect if (!redirect) continue slen = slen < redirect.sourceUrl.length ? redirect.sourceUrl.length : slen tlen = tlen < redirect.targetUrl.length ? redirect.targetUrl.length : tlen ret.push({ source: redirect.sourceUrl, target: redirect.targetUrl, statusCode: redirect.targetStatusCode, }) if (!redirects.length) { count++ redirects = (await client.getRedirects({ limit: 50, offset: count * 50 })) .redirects } } if (!fs.existsSync('out')) { fs.mkdirSync('out') } fs.writeFile( 'out/netlify_redirects', ret.reduce((cur, o) => { return `${cur}${o.source} ${pad(slen, o.source.length)} ${o.target} ${pad( tlen, o.target.length )} ${o.statusCode}\n` }, ''), (e) => { if (e) { // eslint-disable-next-line no-console console.log(e) } } ) } convertUniformToNetlify_redirects()

This results in an output like the following, which should be added to your root folder in Netlify:

/a /b 301 /this /that 301 /product/electronics/walkie-talkie /electronics/walkie-talkie 301 /source/path /target/url 301
import { RedirectClient } from '@uniformdev/redirect' import * as dotenv from 'dotenv' import * as fs from 'fs' const convertUniformTocloudflare = async () => { dotenv.config() const client = new RedirectClient({ apiKey: process.env.UNIFORM_API_KEY, apiHost: process.env.UNIFORM_BASE_URL, projectId: process.env.UNIFORM_PROJECT_ID, }) const ret: any[] = [] let redirects = (await client.getRedirects({ limit: 50, offset: 0 })) .redirects let count = 0 while (redirects.length) { const redirect = redirects.pop() ret.push({ redirect: { source_url: redirect?.redirect.sourceUrl, target_url: redirect?.redirect.targetUrl, status_code: redirect?.redirect.targetStatusCode, preserve_query_string: redirect?.redirect.sourceRetainQuerystring, }, }) if (!redirects.length) { count++ redirects = (await client.getRedirects({ limit: 50, offset: count * 50 })) .redirects } } if (!fs.existsSync('out')) { fs.mkdirSync('out') } fs.writeFile( 'out/cloudflareList.json', JSON.stringify(ret, undefined, ' '), (e) => { if (e) { // eslint-disable-next-line no-console console.log(e) } } ) } convertUniformTocloudflare()

Which results in an output like the following, which should be uploaded into a Cloudflare using their list API .

[ { "redirect": { "source_url": "/a", "target_url": "/b", "status_code": 301, "preserve_query_string": true } }, { "redirect": { "source_url": "/this", "target_url": "/that", "status_code": 301, "preserve_query_string": true } }, { "redirect": { "source_url": "/product/electronics/walkie-talkie", "target_url": "/electronics/walkie-talkie", "status_code": 301, "preserve_query_string": true } }, { "redirect": { "source_url": "/source/path", "target_url": "/target/url", "status_code": 301, "preserve_query_string": true } } ]
import { RedirectClient } from '@uniformdev/redirect' import * as dotenv from 'dotenv' import * as fs from 'fs' const convertUniformTocloudflare = async () => { dotenv.config() const client = new RedirectClient({ apiKey: process.env.UNIFORM_API_KEY, apiHost: process.env.UNIFORM_BASE_URL, projectId: process.env.UNIFORM_PROJECT_ID, }) const ret: any[] = [] let redirects = (await client.getRedirects({ limit: 50, offset: 0 })) .redirects let count = 0 while (redirects.length) { const redirect = redirects.pop() ret.push({ expression: `http.request.uri.path eq "${redirect?.redirect.sourceUrl}"`, description: `Uniform redirect ${redirect?.redirect.sourceUrl} to ${redirect?.redirect.targetUrl}`, action: 'redirect', action_parameters: { from_value: { target_url: redirect?.redirect.targetUrl }, status_code: redirect?.redirect.targetStatusCode, preserve_query_string: redirect?.redirect.sourceRetainQuerystring, }, }) if (!redirects.length) { count++ redirects = (await client.getRedirects({ limit: 50, offset: count * 50 })) .redirects } } if (!fs.existsSync('out')) { fs.mkdirSync('out') } fs.writeFile( 'out/cloudflareRedirects.json', JSON.stringify( { name: 'Uniform Redirect Rules', kind: 'zone', phase: 'http_request_dynamic_redirect', rules: ret, }, undefined, ' ' ), (e) => { if (e) { // eslint-disable-next-line no-console console.log(e) } } ) } convertUniformTocloudflare()

Which results in an output like the following, which should be uploaded into a Cloudflare using their rules API.

{ "name": "Uniform Redirect Rules", "kind": "zone", "phase": "http_request_dynamic_redirect", "rules": [ { "expression": "http.request.uri.path eq \"/a\"", "description": "Uniform redirect /a to /b", "action": "redirect", "action_parameters": { "from_value": { "target_url": "/b" }, "status_code": 301, "preserve_query_string": true } }, { "expression": "http.request.uri.path eq \"/this\"", "description": "Uniform redirect /this to /that", "action": "redirect", "action_parameters": { "from_value": { "target_url": "/that" }, "status_code": 301, "preserve_query_string": true } }, { "expression": "http.request.uri.path eq \"/product/electronics/walkie-talkie\"", "description": "Uniform redirect /product/electronics/walkie-talkie to /electronics/walkie-talkie", "action": "redirect", "action_parameters": { "from_value": { "target_url": "/electronics/walkie-talkie" }, "status_code": 301, "preserve_query_string": true } }, { "expression": "http.request.uri.path eq \"/source/path\"", "description": "Uniform redirect /source/path to /target/url", "action": "redirect", "action_parameters": { "from_value": { "target_url": "/target/url" }, "status_code": 301, "preserve_query_string": true } } ] }

Learn about the CLI commands for redirects in the CLI guide.