# Redirect Client SDK

> How to use the Uniform Redirect Client to manage and resolve URL redirects programmatically.

Source: https://docs.uniform.app/docs/sdk/redirect-client

The Redirect Client (`RedirectClient` from `@uniformdev/redirect`) provides programmatic access to Uniform's redirect management system. Use it to resolve redirects for incoming URLs, manage redirect rules, export redirects to external CDN formats, and build redirect resolution into custom middleware or edge functions.

---

## Installation

```bash
npm install @uniformdev/redirect
```

---

## Initializing the client

```ts
import { RedirectClient } from "@uniformdev/redirect";

const redirectClient = new RedirectClient({
  apiKey: process.env.UNIFORM_API_KEY,
  apiHost: process.env.UNIFORM_CLI_BASE_URL || "https://uniform.global",
  projectId: process.env.UNIFORM_PROJECT_ID,
});
```

---

## Resolving redirects

### Best match

`processUrlBestMatch` finds the single best redirect match for a URL. This is the most common use case for redirect resolution in middleware:

```ts
const result = await redirectClient.processUrlBestMatch("/old-page");

if (result) {
  console.log("Redirect to:", result.url);
  console.log("Status code:", result.definition?.redirect.targetStatusCode);
}
```

#### Using without trie (API-based lookup)

By default, `processUrlBestMatch` queries the API directly. This is suitable for low-traffic scenarios or when you don't need the full redirect set in memory:

```ts
const result = await redirectClient.processUrlBestMatch(
  "/old-page",
  undefined,   // options
  false         // useTrie = false (default)
);
```

#### Using with trie (in-memory lookup)

For high-traffic scenarios, pass `true` for `useTrie` to load all redirects into an in-memory trie structure for instant resolution:

```ts
const result = await redirectClient.processUrlBestMatch(
  "/old-page",
  undefined,   // options
  true          // useTrie = true
);
```

> **Warning:**
>
> The trie approach loads all redirects into memory. This is fast but may use significant memory for large redirect sets (thousands of rules).

### All matches

`processUrlAllMatches` returns all redirect rules that match a URL, not just the best one:

```ts
const results = await redirectClient.processUrlAllMatches("/old-page");

for (const result of results) {
  console.log("Match:", result.url, result.definition?.redirect.targetStatusCode);
}
```

---

## Redirect result object

Both `processUrlBestMatch` and `processUrlAllMatches` return `RedirectResult` objects:

| Property | Type | Description |
| --- | --- | --- |
| `url` | `string` | The resolved target URL |
| `definition` | `RedirectDefinition` | The full redirect rule definition |
| `label` | `string` | (Optional) URL with wildcard segments highlighted in `<em>` tags |
| `lastHop` | `RedirectResult` | (Optional) Previous redirect in a chain |

---

## Redirect definition

Each redirect definition contains:

| Property | Type | Description |
| --- | --- | --- |
| `redirect.sourceUrl` | `string` | Source URL pattern |
| `redirect.targetUrl` | `string` | Target URL |
| `redirect.targetStatusCode` | `number` | HTTP status code (301, 302, etc.) |
| `redirect.sourceMustMatchDomain` | `boolean` | Whether to enforce domain matching |
| `redirect.sourceRetainQuerystring` | `boolean` | Whether to retain the source query string |
| `redirect.targetMergeQuerystring` | `boolean` | Whether to merge query strings |
| `redirect.targetPreserveIncomingDomain` | `boolean` | Whether to preserve the incoming domain |
| `redirect.targetPreserveIncomingProtocol` | `boolean` | Whether to preserve the incoming protocol |
| `metadata.created` | `string` | Creation timestamp |
| `metadata.modified` | `string` | Last modified timestamp |

---

## Redirect options

Both `processUrlBestMatch` and `processUrlAllMatches` accept an options object:

| Option | Type | Description |
| --- | --- | --- |
| `reverse` | `boolean` | When `true`, finds the source URL that could have produced a given target URL |
| `label` | `boolean` | When `true`, returns a `label` property with wildcard segments highlighted in `<em>` tags |

### Reverse lookup

Useful for finding what source URL redirects to a given target:

```ts
const result = await redirectClient.processUrlBestMatch(
  "/new-page",
  { reverse: true },
  true
);

if (result) {
  console.log("This page is redirected from:", result.url);
}
```

---

## Managing redirects

### Get a redirect by ID

```ts
const redirect = await redirectClient.getRedirect({
  id: "redirect-uuid",
});
```

### Get redirects with search and filtering

```ts
const response = await redirectClient.getRedirects({
  sourceUrl: "/old-page",
  limit: 50,
  offset: 0,
  orderBy: "updated_at desc",
});

for (const r of response.redirects) {
  console.log(r.redirect.sourceUrl, "->", r.redirect.targetUrl);
}
```

#### Search parameters

| Parameter | Type | Description |
| --- | --- | --- |
| `sourceUrl` | `string` | Filter by source URL |
| `targetUrl` | `string` | Filter by target URL |
| `search` | `string` | Free-text search across URLs |
| `ids` | `string` | Comma-separated list of IDs |
| `limit` | `number` | Max results per page |
| `offset` | `number` | Pagination offset |
| `orderBy` | `string` | Sort order (e.g., `"updated_at desc"`) |

### Iterate all redirects

For large redirect sets, use the async generator `getAllRedirects` which pages through results automatically:

```ts
for await (const redirect of redirectClient.getAllRedirects()) {
  console.log(
    redirect.redirect.sourceUrl,
    "->",
    redirect.redirect.targetUrl,
    `(${redirect.total} total)`
  );
}
```

### Create or update a redirect

```ts
const redirectId = await redirectClient.upsertRedirect({
  sourceUrl: "/old-path",
  targetUrl: "/new-path",
  targetStatusCode: 301,
});

console.log("Created/updated redirect:", redirectId);
```

### Delete a redirect

```ts
await redirectClient.deleteRedirect("redirect-uuid");
```

---

## Wildcard redirects

Uniform redirects support wildcard segments using the `:paramName` syntax in source URLs. The matched segments are automatically substituted into the target URL:

| Source | Target | Example |
| --- | --- | --- |
| `/blog/:slug` | `/articles/:slug` | `/blog/hello` -> `/articles/hello` |
| `/docs/:version/:page` | `/documentation/:version/:page` | `/docs/v2/intro` -> `/documentation/v2/intro` |

---

## Redirect chain resolution

The client automatically follows redirect chains (where one redirect's target is another redirect's source) with built-in cycle detection. The `lastHop` property on the result provides the previous redirect in the chain:

```ts
const result = await redirectClient.processUrlBestMatch("/page-a", undefined, true);

if (result?.lastHop) {
  console.log("Redirect chain detected:");
  console.log("  First hop:", result.lastHop.url);
  console.log("  Final destination:", result.url);
}
```

---

## Caching with WithMemoryCache

For high-performance scenarios, use the `WithMemoryCache` cache to keep redirect data in process memory with automatic background refresh:

```ts
import { RedirectClient, WithMemoryCache } from "@uniformdev/redirect";

const redirectClient = new RedirectClient({
  apiKey: process.env.UNIFORM_API_KEY,
  projectId: process.env.UNIFORM_PROJECT_ID,
  dataCache: new WithMemoryCache({
    prePopulate: true,       // Load redirects into cache on initialization
    refreshRate: 60_000,     // Refresh cache every 60 seconds
  }),
});
```

### Cache options

| Option | Type | Description |
| --- | --- | --- |
| `prePopulate` | `boolean` | Immediately load redirect data into cache on client creation |
| `refreshRate` | `number` | Interval in milliseconds between automatic cache refreshes. The refresher pauses after 5 idle cycles with no cache reads. |

### Manual cache refresh

```ts
await redirectClient.resetRedirectTrieDataCache();
```

---

## Exporting redirects for external CDNs

Use `RedirectFileConverter` to export Uniform redirects into a format consumable by external CDN redirect systems (e.g., Vercel `vercel.json`, Netlify `_redirects`, Cloudflare):

```ts
import { RedirectFileConverter } from "@uniformdev/redirect";
import fs from "fs";

await RedirectFileConverter({
  redirectEntryObject: (redirect) => ({
    source: redirect.redirect.sourceUrl,
    destination: redirect.redirect.targetUrl,
    statusCode: redirect.redirect.targetStatusCode,
  }),
  wildcardConverter: ({ sourceUrl, targetUrl, sourceWildcards }) => ({
    // Convert Uniform :param wildcards to your CDN's format
    sourceUrl: sourceUrl.replace(/:(\w+)/g, ":$1"),
    targetUrl: targetUrl.replace(/:(\w+)/g, ":$1"),
  }),
  writeFile: (redirects) => {
    fs.writeFileSync("redirects.json", JSON.stringify(redirects, null, 2));
    console.log(`Exported ${redirects.length} redirects`);
  },
});
```

---

## UncachedRedirectClient

For scenarios requiring bypassing the API cache (e.g., admin tools, testing):

```ts
import { UncachedRedirectClient } from "@uniformdev/redirect";

const client = new UncachedRedirectClient({
  apiKey: process.env.UNIFORM_API_KEY!,
  projectId: process.env.UNIFORM_PROJECT_ID!,
});
```

---

## URL validation

The `RedirectClient.validateRedirect` static method checks whether a given URL matches a redirect definition, including domain and query string validation:

```ts
const isValid = RedirectClient.validateRedirect(
  "https://example.com/old-page?ref=123",
  redirectDefinition.redirect
);
```

---

## Import reference

| Export | Package | Description |
| --- | --- | --- |
| `RedirectClient` | `@uniformdev/redirect` | Main redirect client |
| `UncachedRedirectClient` | `@uniformdev/redirect` | Cache-bypassing redirect client |
| `WithMemoryCache` | `@uniformdev/redirect` | In-memory cache with auto-refresh |
| `RedirectFileConverter` | `@uniformdev/redirect` | Export redirects for external CDN formats |
| `ExtractWildcards` | `@uniformdev/redirect` | Extract wildcard segments from URLs |
| `PathTrie` | `@uniformdev/redirect` | Trie data structure for fast path matching |
