Checklist of every redirect introduced by the handle-forward URL refactor (PR #141), for manual verification against a deployed preview. Replace {handle}/{did}/{rkey} with real values.
1. Server 308 redirects — next.config.ts
Old links already shared in the wild should 308 to the new form. :did may be a handle or a DID.
Pre-existing redirects (unchanged, sanity-check still work):
2. pdsls.dev interop 308 — src/proxy.ts
Pasted at-uri paths (domain swapped from pdsls.dev). The literal at:// does a harmless two-hop because the host collapses // first.
3. Client-side canonicalization (router.replace, not an HTTP redirect)
Opening a durable DID link renders, then the address bar swaps to the handle form once resolved.
4. Intentionally NOT redirected
5. Reserved routes unaffected (must still resolve normally)
Notes
- Root-level handles rely on the invariant that handles always contain a dot and app routes never do (see
RESERVED_ROUTES in src/lib/urls.ts). Any new top-level route MUST be a dotless word.
Checklist of every redirect introduced by the handle-forward URL refactor (PR #141), for manual verification against a deployed preview. Replace
{handle}/{did}/{rkey}with real values.1. Server 308 redirects —
next.config.tsOld links already shared in the wild should 308 to the new form.
:didmay be a handle or a DID./profile/{handle}→/{handle}/profile/{did}→/{did}(then client-canonicalizes to/{handle})/profile/{handle}?tab=projects→/{handle}?tab=projects(query preserved)/activity/{did}/{rkey}→/{did}/activity/{rkey}/project/{did}/{rkey}→/{did}/project/{rkey}Pre-existing redirects (unchanged, sanity-check still work):
/search→/explore/connected-apps→/apps/settings/security→/settings,/settings/account→/settings2. pdsls.dev interop 308 —
src/proxy.tsPasted at-uri paths (domain swapped from pdsls.dev). The literal
at://does a harmless two-hop because the host collapses//first./at://{did}/org.hypercerts.claim.activity/{rkey}→ ... →/{did}/activity/{rkey}/at:/{did}/org.hypercerts.claim.activity/{rkey}→/{did}/activity/{rkey}/at/{did}/org.hypercerts.claim.activity/{rkey}→/{did}/activity/{rkey}/at/{did}/org.hypercerts.collection/{rkey}→/{did}/project/{rkey}/at/{did}/app.certified.actor.membership/{rkey}→/{did}(page-less collection → profile)at(e.g./atrium.bsky.social) is NOT caught by the proxy3. Client-side canonicalization (router.replace, not an HTTP redirect)
Opening a durable DID link renders, then the address bar swaps to the handle form once resolved.
/{did}→ address bar becomes/{handle}/{did}/activity/{rkey}→/{handle}/activity/{rkey}/{did}/project/{rkey}→/{handle}/project/{rkey}/{did}/activity/{rkey}/edit→/{handle}/activity/{rkey}/edit4. Intentionally NOT redirected
/activity/{did}/{rkey}/editand/project/{did}/{rkey}/edit→ graceful "Not found" page (owner-only, never shared; 200 with not-found UI, not a hard 404)5. Reserved routes unaffected (must still resolve normally)
/settings,/explore,/groups,/create,/project/new,/groups/create,/notifications,/welcomeNotes
RESERVED_ROUTESinsrc/lib/urls.ts). Any new top-level route MUST be a dotless word.