# Pitfalls — ClientHub Freelancer Client Portal **Domain:** Freelancer client portal with secret-link access, solo developer, Vercel deploy **Researched:** 2026-05-09 **Confidence:** HIGH --- ## Critical Pitfalls ### 1. Secret Links That Are Guessable or Enumerable **What goes wrong:** If client tokens are generated from names, sequential IDs, or short strings, they can be enumerated. `/client/mario-rossi` or `/client/3` are not secrets. **Prevention:** - Generate tokens as cryptographically random UUIDs (v4) or nanoid (21 chars / ~126 bits of entropy) - Never derive from name, company, or sequential ID - Never log or display full tokens in admin analytics **Warning signs:** Client slug contains the client's name. Token under 20 characters. Link can be reconstructed from info the client already knows. **Phase mapping:** Phase 1 — data model + routing. Cannot be retrofitted after links are distributed. --- ### 2. Client API Exposes Admin Data (Hidden in UI Only) **What goes wrong:** Developers fetch all client data and conditionally render fields. A technical client opens DevTools and sees the full quote breakdown — the exact thing the product prevents. **Prevention:** - Define two distinct data shapes: `ClientView` and `AdminView` - Client API routes (`/api/c/[token]/`) return `ClientView` only — enforced server-side, not in the UI - `accepted_total` goes on the `clients` row. Client API never queries `quote_items` **Warning signs:** Client API response includes `lineItems` filtered in the frontend. "Client sees only total" enforced with `display: none`. **Phase mapping:** Data model and API shape — Phase 1. --- ### 3. Data Loss from Vercel Filesystem / In-Memory Storage **What goes wrong:** Vercel serverless functions are stateless. File writes to local filesystem and in-memory state are lost between invocations. SQLite on disk silently vanishes after cold start. Bug only manifests in production. **Prevention:** - External persistent DB from day one: Neon (Postgres free tier) - Never write to `fs` for persistent data on Vercel - Validate persistence in the first production deploy **Warning signs:** Project stores data in a `data/` JSON directory. SQLite without a remote adapter. Data changes in one request not visible in the next. **Phase mapping:** Day-one infrastructure decision — Phase 1, before any real data is entered. --- ### 4. Over-Engineering Before the First Client Uses It **What goes wrong:** Building the Claude onboarding flow and quote generator before a single client has opened their dashboard. Real clients still managed via email while the portal is "almost ready." **Prevention:** - Hard success criterion for Phase 1: one client link is shareable and works - Phase 1 ships read-only client dashboard only - Collect direct feedback from one real client before adding features **Warning signs:** >2 weeks pass without a shareable client link. Claude integration started before admin edit UI exists. **Phase mapping:** Enforced by roadmap phase ordering. --- ### 5. Token Is the Primary Key (Unrotatable) **What goes wrong:** A client forwards their link. The link appears in a screenshot. There is no mechanism to rotate without losing data or breaking bookmarks. Worse if token = primary key: rotation requires a migration. **Prevention:** - Data model: stable internal UUID as PK; secret token is a separate, independently updatable field - Build "Regenerate link" in admin area during Phase 2 — it's a single field UPDATE - Overwriting the token field is sufficient to invalidate the old link **Warning signs:** Token is used as PK of the client record. No admin affordance to regenerate a link. **Phase mapping:** Data model separation — Phase 1. Admin rotation UI — Phase 2. --- ### 6. Client Approval Has No Immutable Record **What goes wrong:** Client clicks "Approve." Later: "I never approved that." If approval is a boolean with no timestamp, there is no evidence. Weakens your position in commercial disputes. **Prevention:** - Store `approved_at` timestamp alongside the approval boolean — from day one - Display approval timestamp in admin view - Approvals are immutable for clients — no "undo" button **Warning signs:** Approval is a boolean column with no timestamp. Client-visible "undo approval" button exists. **Phase mapping:** Schema — Phase 1. Display in admin — Phase 2. --- ## Moderate Pitfalls ### 7. Payment Status Without a Valid State Machine Payment has three states (da_saldare / inviata / saldato) for two payments. If transitions are not enforced, the dashboard shows contradictory states. **Prevention:** Enforce valid transitions: `da_saldare → inviata → saldato`. Admin UI offers only valid next states. **Phase mapping:** Admin UI — Phase 2. --- ### 8. Google Drive Links That Rot Document links break when sharing settings change or the Drive account changes. Client sees a broken link to their own deliverable. **Prevention:** Store a display name alongside the URL so broken links are visible in admin. Build a simple "update link" affordance in Phase 2. --- ### 9. Admin Area With No Real Access Control `/admin` is a secret route with no authentication. A client who guesses the URL accesses all client data. **Prevention:** Add Next.js middleware check against `ADMIN_PASSWORD` env variable before Phase 2 ships. Never rely on security through obscurity for a route that contains all client data. **Phase mapping:** Phase 2, before admin area contains real data. --- ### 10. Comments Scope Creeping Into Threading "Clients can leave comments" becomes complex when replies, read/unread state, and notifications are added. Can double Phase 1 scope. **Prevention:** Phase 1 ships comments as a flat append-only list per task. No threading, no replies, no email notifications. --- ## Minor Pitfalls ### 11. DNS Configuration as a Last-Minute Task `welcomeclient.iamcavalli.net` requires a CNAME to Vercel's DNS. Propagation takes minutes to hours. Doing this the day of a client demo misses the deadline. **Prevention:** Configure DNS in Phase 1 before the UI is complete. Verify propagation independently. --- ### 12. Mobile Responsiveness as an Afterthought Clients open the link on their phone from a shared message. A broken mobile layout is the first impression. **Prevention:** Use Tailwind mobile-first defaults from the start. Test on a real phone before any link is sent. --- ### 13. No Empty States A new client record with no tasks shows a blank page. The client assumes something is broken. **Prevention:** Design minimal empty states for no-tasks, no-documents, no-comments. --- ## Phase-Specific Warnings Summary | Phase Topic | Likely Pitfall | Mitigation | |---|---|---| | Client token generation | Guessable slug from name | Crypto-random UUID/nanoid, never name-derived | | Client-facing API | Admin data in JSON response | `ClientView` type enforced server-side | | Storage choice | Vercel filesystem not persistent | External DB (Neon) before first data write | | Admin area access | No real auth | Middleware check before Phase 2 ships | | Approval recording | Boolean-only, no audit trail | Store `approved_at` from day one | | Token in data model | Token = PK, unrotatable | Separate stable ID from rotatable token field | | Phase ordering | Claude flow before dashboard | Enforce: client view → admin edit → Claude | | Comments | Threading scope creep | Flat list in Phase 1 | | DNS | Last-minute failure | Configure and verify in Phase 1 |