---
title: When an address changes
description: Routes match on values your team can edit. We keep what those values used to be, so your site can answer a stale link instead of losing the reader.
---

Your site routes on something in your content: a slug, a code, a key.
That value belongs to the people writing the content, and sooner or later
one of them changes it. They are allowed to. A page gets renamed, a product
gets repositioned, a typo gets fixed three months after launch.

Every link anybody saved, sent or indexed then points at a value nothing
publishes. Your site cannot tell that apart from an address that never
existed, so both come back as a 404 and the reader leaves.

ZevContent keeps the previous values, and tells you when you ask.

> **INFO: Your routing stays yours**
>
> We do not issue redirects and we never will. We cannot see your routes,
> your locales or your rules, and a CMS guessing at those would be worse
> than one that stays out of the way. What we can do is tell you the fact:
> this value used to belong to that entry, and it changed on this date.
> Sending a 301, a 410 or a 404 is your call.

## Mark what your code reads

Only fields you declare are kept. Set `frontend_contract` on the field
your routes match:

```json
{ "key": "slug", "label": "Address", "type": "text", "required": true,
  "frontend_contract": true,
  "help_text": "Your routes match on this." }
```

That one flag does two jobs. It makes the dashboard present the field as a
value with a pen rather than an open input, so nobody edits it by accident
(see [Navigation and mega menus](/concepts/navigation-menus)). And it is
how we know the value is worth remembering.

## Ask what an address used to be

```bash
curl -G https://api.zevcontent.net/v1/content/page/lookup \
  --data-urlencode "field=slug" \
  --data-urlencode "value=/services/managed-it/networks-and-connectivity" \
  -H "Authorization: Bearer $ZEVCONTENT_KEY"
```

Three answers, and your router branches on `match`.

**It is still live.** Render it.

```json
{ "match": "current",
  "entry": { "id": "ent_68f...", "type": "page",
             "url": "/v1/content/page/ent_68f..." } }
```

**It moved.** `current_value` is where it lives now, so you can redirect
without a second request.

```json
{ "match": "previous",
  "replaced_at": "2026-09-13T15:33:46.914Z",
  "current_value": "/services/managed-it/network-operations",
  "entry": { "id": "ent_68f...", "type": "page",
             "url": "/v1/content/page/ent_68f..." } }
```

**Nothing ever published it.** `404`, and this one really is a 404.

### In a router

```js
const res = await fetch(
  `${API}/v1/content/page/lookup?field=slug&value=${encodeURIComponent(path)}`,
  { headers: { Authorization: `Bearer ${key}` } },
);

if (res.status === 404) return notFound();

const { data } = await res.json();
if (data.match === 'previous') {
  return redirect(data.current_value, 301);
}
return render(data.entry);
```

Call it only when your normal lookup missed. A request that matched a live
page has nothing to ask.

## What is kept, and when

**Only on publish.** A draft has never been served, so a value that only
existed in an unpublished draft is not history, it is typing.

**Only the fields you marked.** Keeping every previous value of every field
would be a second copy of your project, to answer a question nobody asks of
a headline.

**The most recent claim wins.** A value can be handed around: a slug one
page gives up can be taken by another and given up again. The lookup
answers with the entry that gave it up most recently, which is the one a
reader following an old link was most likely looking at.

**History starts when you mark the field.** We cannot record what a value
used to be before we were asked to watch it, so set `frontend_contract`
when you define the field rather than after the first rename.

> **WARNING: A value that is never edited needs none of this**
>
> If your routes match on the entry's `ent_…` id, nothing can move: an id is
> issued once and cannot be changed. Use a slug when the address should read
> well, and use the id when it only has to be right.