- Rename src/app/c/[token] → src/app/client/[token] - Update proxy.ts, ClientRow, admin client detail with /client/ path - Add output: "standalone" to next.config.ts for Docker build - Add Dockerfile (multi-stage, node:20-alpine) and .dockerignore - Push schema to Coolify Postgres via SSH tunnel (drizzle-kit push ✓) - Update CLAUDE.md constraint 4 to reflect /client/ route - Add Phase 4 planning artifacts (04-00, 04-RESEARCH, 04-PATTERNS) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7.4 KiB
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:
ClientViewandAdminView - Client API routes (
/api/c/[token]/) returnClientViewonly — enforced server-side, not in the UI accepted_totalgoes on theclientsrow. Client API never queriesquote_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
fsfor 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_attimestamp 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 |