Redirects and link health
Sync the workspace redirect table to your edge or middleware, and report 404s back to Clog.
Clog doesn't host your site, so it can't issue 301s for you. Instead, it ships you the redirect table and accepts 404 reports back. The two ends of this loop are:
GET /external/redirects— pull the redirects your edge / middleware should replay.POST /external/link-health— when your site serves a 404, report it back so the dashboard can resolve it.
Together they keep your URL structure healthy without manual coordination.
Where redirects come from
Two paths populate the workspace's redirect table:
- Auto-created on slug changes. Whenever a post or page slug changes, the backend writes a
Permanent301row from the old slug to the new one. You don't have to do anything — sync the table and the redirect appears. - Manually created in the dashboard. The Redirects manager (
/workspaces/[id]/redirects) lets editors create one-off entries or bulk-import a CSV. Useful for legacy URLs, vanity links, and one-way410 Gonemarkers.
Sync redirects to your edge
GET /external/redirects returns a paginated list. Pull it on a schedule (at build, on a webhook-free poll, etc.) and replay matches locally — typically in your edge middleware so the 301 fires before your application code runs.
curl 'https://api.clog.dev/api/v1/external/redirects?pageSize=500&order=asc&orderBy=fromPath' \
-H "X-API-Key: clog_live_xxx"Response — a standard PaginatedResponse<RedirectResponse>:
{
"data": [
{
"id": "...",
"workspaceId": "...",
"fromPath": "/posts/old-slug",
"toPath": "/posts/new-slug",
"type": "Permanent301",
"hitCount": 12,
"lastHitAt": "2026-05-22T10:11:12.000Z",
"createdAt": "...",
"updatedAt": "..."
}
],
"page": 1,
"pageSize": 500,
"order": "asc",
"orderBy": "fromPath",
"total": 87,
"totalPages": 1
}| Field | Notes |
|---|---|
fromPath | Always starts with /. Match against request.nextUrl.pathname (or its equivalent in your framework). |
toPath | Starts with / (relative) or http(s)://... (absolute). Pass through verbatim to the redirect response. |
type | Permanent301 (the default and most common), Temporary302, Temporary307, Gone410, UnavailableForLegalReasons451. Map each to the matching HTTP status. |
hitCount | The dashboard's view of how often this redirect fired (Clog increments it from your lastHitAt updates — but v1 does not require you to report hits back, it's bookkeeping the dashboard does separately when you list). |
Required permission: settings:read OR seo:read.
Filtering
| Param | Purpose |
|---|---|
search | Case-insensitive substring on fromPath and toPath. |
type | One of the RedirectType values above. |
page, pageSize, order, orderBy | Standard pagination. orderBy allow-list: `createdAt |
A Next.js edge-middleware example
import { NextRequest, NextResponse } from 'next/server';
// Built at deploy time or fetched at startup — see the worked example for details.
const REDIRECTS: { fromPath: string; toPath: string; type: RedirectType }[] =
await loadRedirectsFromClog();
const STATUS: Record<RedirectType, number> = {
Permanent301: 301,
Temporary302: 302,
Temporary307: 307,
Gone410: 410,
UnavailableForLegalReasons451: 451,
};
export function middleware(req: NextRequest) {
const hit = REDIRECTS.find(r => r.fromPath === req.nextUrl.pathname);
if (!hit) return NextResponse.next();
if (hit.type === 'Gone410' || hit.type === 'UnavailableForLegalReasons451') {
return new NextResponse(null, { status: STATUS[hit.type] });
}
const dest = hit.toPath.startsWith('http')
? new URL(hit.toPath)
: new URL(hit.toPath, req.nextUrl.origin);
return NextResponse.redirect(dest, STATUS[hit.type]);
}
export const config = { matcher: '/((?!_next|api).*)' };How often you re-sync depends on your edit cadence. For a build-time-only fetch (typical for marketing sites), you'll lose the auto-301 window between an editor renaming a slug and your next deploy — a 5–60 minute poll closes that gap. Webhooks aren't shipped in v1.
Report 404s back to Clog
When your site serves a 404, POST /external/link-health so the dashboard's Link health inbox sees it.
curl -X POST https://api.clog.dev/api/v1/external/link-health \
-H "X-API-Key: clog_live_xxx" \
-H "Content-Type: application/json" \
-d '{ "path": "/posts/missing-thing", "referrer": "https://example.com/sitemap" }'Body shape:
{
path: string; // the missing path (no host, must start with /)
referrer?: string; // optional — the document that linked here, full URL
}The endpoint upserts on (workspaceId, path) and increments hitCount. Repeat reports for the same path are explicitly safe — fire-and-forget.
// app/not-found.tsx (or your framework's 404 page)
export default async function NotFound() {
await fetch('https://api.clog.dev/api/v1/external/link-health', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.CLOG_API_KEY!,
},
body: JSON.stringify({ path: usePathname() }),
}).catch(() => { /* don't crash the 404 page on a reporting failure */ });
return <h1>Not found</h1>;
}Report from your server, not the browser. The API key is a workspace credential — never ship it to the client. Either fire the report from a route handler / middleware / Next.js Server Component, or proxy through your own backend.
Resolving a broken link
The dashboard's Link health inbox lets an editor turn an entry into:
- A redirect — creates a
Redirectrow on the spot (will surface on your nextGET /external/redirectssync) and stampsresolvedBy = "redirect:<id>". - Fixed — the editor restored the page; nothing more to do.
- Ignored — leave it as 404 deliberately.
All three set resolvedAt = now(). Resolution happens in the dashboard, not via the external API.
Permission
POST /external/link-health requires only workspace membership (any bound key works). It's intentionally low-friction — consumer sites need to report every 404 without per-route permission tuning.
A typical loop
- Editor renames
/posts/old-slug→/posts/new-slugin the dashboard. Backend writes aPermanent301row. - Your edge middleware re-syncs and starts replaying the redirect.
- An older crawler still requests
/posts/old-slug→ your middleware 301s it → all good. - Some external site links to
/posts/typo-slug→ your site 404s → middleware (or your 404 page) POSTs to/external/link-health. - Editor sees the typo in the Link health inbox, creates a redirect to
/posts/correct-slugfrom there. - Next sync picks up the new redirect; the 404 stops happening.
Related
- Endpoint reference → Redirects — full query and response schemas.
- Endpoint reference → Link health — POST shape.
- Reference → Error codes — what
BAD_REQUESTlooks like on a malformed report.