DEV Community

Gaurav Guha
Gaurav Guha

Posted on Originally published at thefrontkit.com

How to architect a Next.js CRM that handles real sales workflows

A CRM looks like a table of contacts with some pipelines. It's actually a dozen interconnected entities: contacts, companies, deals, activities, tasks, notes, emails: with permissions, custom fields, automation, and reporting layered on top. Building one in Next.js from scratch is realistically a four-month project.

What a real CRM has to do

Contacts and companies as linked entities

A contact belongs to a company. Companies have many contacts. Most early builds treat them as flat records with a "company" text field. That fails the moment you need to view all contacts at one company, see deal totals per company, or merge duplicate companies.

Build them as first-class entities from day one. Contact has a company_id foreign key. Company has aggregated counts and totals computed at query time or cached.

Pipeline as a kanban board and more

The deal pipeline is the heart of the CRM. Users expect drag-and-drop between stages with counts and values per stage. But the pipeline is also a forecast showing weighted revenue by close date, a funnel chart for conversion rates, a leaderboard for wins per rep, a history of stage durations, and a filter target for queries like "deals over $50k closing this month."

Build the data model around stages, deal value, close date, owner, and stage-change timestamps. Every view above comes from variations on those fields.

Activity timeline

Every contact and deal needs a timeline of calls logged, emails sent, meetings attended, notes added, stage changes, file uploads, owner reassignments. The timeline is read 10x more than it's written, so build it for read performance.

Pattern: an activities table with type, actor_id, subject_id, subject_type, metadata (JSONB), and created_at. Index by subject so contact timelines and deal timelines query fast.

Tasks linked to deals and contacts

Reps live in tasks. Every CRM needs:

  • Create a task linked to a contact, deal, or both
  • Set a due date, owner, priority
  • See "my tasks today / this week / overdue"
  • Mark complete with a timestamp
  • Recurring tasks for follow-ups
  • Bulk operations (assign 20 leads to one rep)

Tasks usually become the most-used feature after the pipeline. Underbuilt task functionality is the most common complaint about early CRMs.

Custom fields

You'll be asked for them within two weeks of launch. The pattern:

  • Each entity (contact, deal, company) supports user-defined custom fields
  • Field types: text, number, date, dropdown (single and multi), checkbox, file
  • Fields are configurable from settings, not hardcoded
  • Custom fields appear in detail view, table view as toggleable columns, and in filters

Schema pattern: a custom_fields definition table and a custom_field_values table with polymorphic associations. Or JSONB on each entity with a separate schema definition. Both work; JSONB is simpler for small scale.

Reporting

CRM reports are how managers measure their team. The minimum:

  • Pipeline value by stage with trend over time
  • Activities per rep (calls, emails, meetings logged)
  • Win rate by rep, by source, by deal size
  • Forecast (weighted pipeline by expected close date)
  • Aging report (deals stuck in stages too long)
  • Lost reasons breakdown

These are the same charts every sales leader has used for 30 years. Don't innovate here; ship the standards.

Permissions and visibility

Real CRMs handle this:

  • Reps see their own deals by default
  • Managers see their team's deals
  • Admins see everything
  • Some objects (companies, contacts) are shared; others (notes, certain deals) are private

Build a permissions layer at the server-action or API-route level. Frontend filtering alone is a security incident waiting to happen.

Tech stack: CRM-specific decisions

The general Next.js stack applies (Next 16, App Router, Tailwind v4, shadcn/ui). The CRM-specific additions:

Drag-and-drop: @hello-pangea/dnd or dnd-kit for the pipeline kanban. Custom implementations are not worth the time.

Charts: Recharts for everything except complex Gantt. Tremor is fine if you want pre-built dashboard primitives. Avoid D3 unless you have a senior visualization engineer.

Email integration: Resend for outbound, Postmark inbound parsing if you need to log replies into the timeline. Plan for OAuth into Gmail/Outlook for two-way sync at v2.

Search: Postgres tsvector full-text search is enough for v1. Algolia or Typesense at scale (>50k records per workspace).

File storage: R2 or Supabase Storage. CRMs accumulate files (proposals, contracts, business cards) faster than you'd expect.

Audit log: A standalone audit_log table. Compliance asks for who-edited-what timestamps; building this retroactively is painful.

Build path 1: from scratch (12 to 16 weeks)

The realistic order:

Week 1-2: Foundation. Next.js setup, auth (Clerk or Auth.js), basic app shell, database schema for users and teams.

Week 3-4: Contacts and companies. Tables, detail views, create/edit forms, search. Get the data model right; everything else builds on this.

Week 5-6: Deals and pipeline. Deal entity, stages, kanban board with drag-and-drop, deal detail page.

Week 7-8: Activities and tasks. Activity timeline, task model, task list views, due date logic.

Week 9-10: Custom fields. Definition UI, value storage, integration into table views and detail pages.

Week 11-12: Reporting. The 6-7 standard reports above. Charts on Recharts.

Week 13-14: Permissions. Role model, server-action checks, filtered queries.

Week 15-16: Polish. Empty states, error states, dark mode, accessibility, performance, settings.

Realistic for most teams is 16 to 20 weeks because every entity has more edge cases than it looks.

Build path 2: using a CRM dashboard kit (1 to 2 weeks)

A dashboard kit ships pre-built screens for contacts with company linking, companies with aggregated deal totals, deals with kanban pipeline (drag-and-drop), list view, calendar view, forecast, activity timeline pattern wired across contacts and deals, tasks with due dates, owners, priorities, and filtered views, custom field architecture (definition + values), reports (pipeline, activities, win rate, forecast), and permissions-aware components.

Work that remains:

  • Wire your auth provider into the existing screens
  • Connect your database, replace seed data with real queries
  • Add your industry-specific custom fields and pipeline stages
  • Build any unique automation logic on top of the existing chrome

Step-by-step from the kit

Day 1: Setup and brand. Clone, install, run. Replace logo, colors, brand name. Update copy in marketing pages.

Day 2-3: Database. Set up Postgres. Apply the schema. Migrate seed data to a real CRM dataset (your own contacts as a starting point).

Day 4-5: Auth. Wire Clerk, Auth.js, or Supabase Auth into the existing login/signup screens.

Day 6-7: Pipeline configuration. Set up your sales stages. Match them to the kit's stage entity. Test the kanban drag-and-drop with real deal data.

Day 8-9: Custom fields. Add the fields specific to your industry (e.g., "lead source," "deal currency," "renewal date" for SaaS).

Day 10: Reports. Validate the built-in reports work with your data. Tweak filter defaults to match your team's workflow.

Day 11-12: Permissions. Define your team's role structure. Wire role checks into the existing permission layer.

Day 13-14: Deploy and beta. Vercel deploy, custom domain, beta test with your sales team.

Common pitfalls

Treating contacts and companies as one entity. You'll merge duplicates, you'll filter by company, you'll show deal totals per company. Build them as separate linked entities from the start.

Hardcoding pipeline stages. Every sales team has different stages. Storing stages as a configuration entity (not an enum) means new customers can define their own without a code change.

Skipping the activity timeline until "later". Sales reps live in the timeline. A CRM without it is a database tool. Build the timeline pattern with the first entity you ship.

Ignoring search until you have data. Search across contacts and deals is the second-most-used feature after the pipeline. Build full-text search from day one with tsvector; upgrade to Algolia or Typesense when scale demands it.

Underbuilding custom fields. Custom fields are not optional. Build the architecture from day one even if you ship with only three standard fields. Retrofitting custom fields into a hardcoded schema is a one-month project.

Forgetting bulk operations. Reps need to assign 20 leads at once, change owner on a batch of deals, mark a list of tasks complete. Build bulk action UI with the first table you build.

Skipping audit logs. Compliance will ask. Build the audit_log table on day one and append to it from every server action.


This is a shorter version of a guide I wrote at Frontkit Studio. The full post has the complete walkthrough.

Top comments (0)