Slug-Based Viewer URLs for Artidrop — Design Analysis
Date: April 15, 2026
Scope: Feasibility and design options for artidrop.ai/a/{slug} viewer URLs
Background
Today, every artifact on Artidrop gets a viewer URL of the form /a/{shortId}, where shortId is a 10-character base62 string auto-generated at publish time (e.g. /a/a1b2c3d4e5). The proposal is to let users set a human-readable slug so a published artifact can be reached at a memorable URL like /a/my-cool-project.
Current Architecture
Artifact Identifiers
Each artifact carries two IDs:
| Field | Format | Purpose |
|---|---|---|
id |
art_xxxxxxxx (8-char base62) |
Internal DB primary key |
shortId |
xxxxxxxxxx (10-char base62) |
Current public viewer URL |
slug |
[a-z0-9-]+ text, optional |
Idempotent publish key (upsert), not in viewer URL |
The slug field already exists in the schema with a per-owner unique constraint: UNIQUE(ownerId, slug). It is validated against the pattern ^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$ and is already accepted by the CLI via -s / --slug. However, it is only used server-side as an upsert key — it never appears in the viewer URL returned to the user.
URL Flow
Publisher API Viewer
artidrop publish foo.md → POST /v1/artifacts → /a/{shortId}
--slug my-report slug stored, but (shortId in URL,
url = /a/{shortId} not slug)
Content is served from a separate domain (content.artidrop.com) under the same /{shortId}/v/{version} structure. The app domain (artidrop.ai) hosts the viewer shell at /a/{shortId}.
Design Options
Option A — Global Slug (Simplest URL)
URL shape: /a/my-cool-project
Make slugs globally unique (add a global UNIQUE(slug) constraint, drop or retain the per-owner one). The first user to claim a slug owns it.
Pros: Clean, short, shareable URLs.
Cons: Squatting risk; slug conflicts across users require a resolution strategy; schema migration needed to enforce global uniqueness on a column that previously allowed per-owner collisions.
Option B — User/Slug Namespaced URL
URL shape: /a/alice/my-cool-project
Keep per-owner uniqueness. Requires surfacing a username in the schema (not currently present — only userId exists).
Pros: Unambiguous; no squatting; mirrors GitHub's model.
Cons: Longer URLs; requires adding a username field to users and a registration flow.
Option C — Slug as Redirect to ShortId (Recommended)
URL shape: /a/my-cool-project → 301 → /a/a1b2c3d4e5
The shortId URL remains canonical. A slug lookup hits the DB, finds the shortId, and redirects. Old shortId links never break. If a user renames their slug, the old slug 404s but the shortId link still works.
Pros: Safest for link longevity; no canonical URL ambiguity for crawlers; slug rename doesn't silently serve stale content.
Cons: Two hops for slug-based access; the "pretty" URL isn't what ends up in the browser bar after redirect.
Option D — Slug as Alias (Both URLs Serve Identically)
Both /a/{slug} and /a/{shortId} return 200 with identical content. Use a <link rel="canonical" href="/a/{shortId}"> tag so search engines index only one.
Pros: Slug URL stays in browser bar; no redirect hop.
Cons: Two live URLs for the same content; canonical tag must be consistently set or crawlers will split page rank.
Recommended Approach
Option C (redirect) with globally unique slugs is the lowest-risk path:
- Add a global unique index on
slug(or enforce uniqueness at the application layer with a clear error message on conflict). - In the pages route (
/a/:idinpages.ts) and the API route (/v1/artifacts/:idinartifacts.ts), add a third lookup branch: try slug after shortId fails. - In the pages route, if the lookup resolved via slug, issue a
301to the shortId-based canonical URL. - In the API response, emit
"url": "/a/{slug}"when a slug is set,"/a/{shortId}"otherwise. - Add a slug-edit input to the artifact detail panel on the dashboard with live validation.
Implementation Scope
| Area | Change | Effort |
|---|---|---|
| DB schema | Global unique index on slug |
Trivial (one migration) |
artifacts.ts API route |
Lookup by slug; return slug-based URL when set | ~20 lines |
pages.ts server route |
Slug → 301 to shortId | ~15 lines |
ArtifactView.tsx + App.tsx |
No change needed if API resolves slugs | None |
| Dashboard UI | Slug edit field + validation + URL preview | ~1 day |
| Share menu | Emit slug URL when available | ~10 lines |
Total estimate: 1–2 days.
Open Questions
- Slug conflict UX: What error message when a slug is already taken? First-come-first-served or per-user namespacing?
- Slug on existing artifacts: Should users be able to add a slug to artifacts they published before this feature? (Yes, via the dashboard edit panel.)
- Slug in CLI output: After
artidrop publish --slug foo, should the CLI print/a/fooor/a/{shortId}? Slug-based URL is friendlier but only works after the redirect is in place. - Anonymous artifacts: Currently anonymous publishers cannot use slugs. Keep that restriction?
Conclusion
The slug field is already in the schema and the CLI already accepts it — the infrastructure is 80% there. The remaining work is wiring slug lookups into the viewer URL resolution path, deciding on global vs. per-owner uniqueness, and building the dashboard UI for slug editing. The redirect approach keeps existing links safe and avoids canonical URL complexity.