[Deep Dive #27] Random Slug Governance - discussion
We’ve been debating random slugs for a while and the strongest argument is honestly: “the slug is transport, meaning is state”. But in practice, teams still smuggle meaning into the URL shape.
If you adopt random slugs, what governance keys do you require so the route story stays stable across segments? Do you treat the URL as a hint and route state as truth after render, even for deep links? What evidence do you render so a random-slug screen is debuggable without reading router glue?
Comments (10)
Back to latestRandom slugs are fine. Random *stories* are not. Governance keys are non-negotiable if you don’t want chaos.
The “URL as hint, route state as truth” posture is the only one that stays sane once you have back/forward + programmatic navigation.
We render lane/posture/tab + lastSlug so support can always see what contract is active.
Related: Routing and Navigation and Navigation Routines (Deep Dive #4).
We made governance a shell component with one visible marker:
tsx
export function GovernanceShell({ children }: { children: React.ReactNode }) {
const gov = useGlobalRoute('cases', { lane: 'random-slug', posture: 'state-first', tab: 'details', lastSlug: '' });
return (
<section data-lane={gov.read('lane')} data-posture={gov.read('posture')} data-tab={gov.read('tab')}>
{children}
</section>
);
}Counterpoint: random slugs can make sharing links feel hostile.
If the URL is truly meaningless, users will copy it and it won’t tell them anything (and they’ll assume the app is broken).
Yeah, that’s my hesitation. The post’s answer seems to be “render meaning explicitly in the UI and store it in route state”.
Which is kind of the point: stop using the URL as a UI spec.
Fair. If you render the meaning, the URL can be transport without being confusing.
Deep linking still works if you treat the slug as a lookup key and then render the resolved meaning.
The failure mode is when devs start encoding meaning back into the slug for convenience.
Governance keys we require: lane, posture, tab, and a stable “resolved id” for the underlying entity.
Slug is allowed to change; resolved id is the durable truth.
Evidence ledger is the underrated piece. If you have random slugs, you need narratability.
tsx
const ledger = useLiveState('case-ledger', { events: [] as { at: number; kind: string; value: string }[] });
ledger.write('events', [...ledger.read('events'), { at: Date.now(), kind: 'slug', value: hint.read('slug') }]);
return <div data-events={ledger.read('events').length} data-slug={hint.read('slug')} />;The part that felt wild to me was “programmatic navigation in render”.
But once you see it as a governed posture (and you render the posture), it becomes less spooky.
If you can’t answer “why did this random slug render this screen” from a screenshot, you will hate operating the app.
Random slug governance is basically a contract discipline story.
The slug is the transport, the contract keys are the meaning, and the evidence ledger is the audit trail.