Skip to content
lazy devs
5 min readLazy Devs

Building a customer portal customers actually use

What it really takes to ship a customer portal people log into on purpose: scope, timelines, rough costs, and the traps that turn portals into ghost towns.

Most customer portals get built, launched, and then quietly ignored. The login page collects dust while customers keep emailing your support inbox instead, because the portal does less than the email does. A portal that customers actually use is not a bigger feature list. It is a smaller set of jobs done so well that logging in beats every other option.

Start with the one job they keep emailing you about

Before anyone opens Figma, go read your last three months of support tickets and invoices. The portal you should build is hiding in there. If half your inbound is "can you send me the latest invoice" or "what's the status of my order," that is your first screen. Not a dashboard with twelve widgets. One screen that answers the question people actually ask.

A good customer portal usually does three to five of these well: see account and billing history, download invoices and receipts, check the status of an order or project, update contact and payment details, open and track a support request, and manage team members or seats. Pick the two or three that map to your real support volume. Everything else is a phase two you can scope once the first version earns its keep.

The tell that you are overbuilding: a feature nobody can connect to a specific email they are tired of answering. If you cannot name the message it replaces, it is decoration.

Why "self-serve" only works if it is faster than asking a human

Customers will use a portal when it is genuinely quicker than firing off an email and waiting. That sounds obvious, but it sets a hard bar. If downloading an invoice takes four clicks and a password reset, people email you instead, and now you maintain a portal and answer the tickets. Every flow has to beat the lazy alternative of "just email someone." Design for the impatient customer at 11pm, not the patient one during business hours.

The architecture decisions that actually matter

You do not need anything exotic. A typical portal we build runs on Next.js for the frontend and API routes, Postgres for data, and a hosted auth provider so you are not writing your own login. The interesting decisions are not the framework. They are these three.

Auth and identity. Use a real auth provider (Clerk, Auth0, or WorkOS if you sell to enterprises that want SSO). Rolling your own password reset and session handling is where small projects quietly bleed weeks and create security holes, and getting the authentication patterns right early saves you from painful retrofits. Magic links or email plus password covers most B2C. B2B usually needs organizations, roles, and an invite flow, which is a meaningful jump in scope, so decide early.

Where the data lives. Most portals are a read-only window onto systems you already have: Stripe for billing, your ops database for order status, a CRM for account details. Decide per data source whether you query it live or sync it into your own Postgres. Live is simpler to reason about but ties your portal's uptime to a third party. A nightly or webhook-driven sync is more work but makes the portal fast and resilient. For billing, lean on Stripe's customer portal for the heavy lifting (payment methods, invoices, subscription changes) and only build custom screens where Stripe's hosted pages do not fit your brand or flow.

Permissions. The day you have team accounts, you need to answer "can this user see this thing." Get the access rules right at the database query layer, not in the UI. Hiding a button is not security. The query that fetches invoices should filter by the logged-in user's organization, full stop.

// Scope every query to the caller. Never trust an id from the request body.
const invoices = await db.invoice.findMany({
  where: { organizationId: session.user.orgId },
  orderBy: { issuedAt: "desc" },
});

That one habit, scoping at the query, prevents the most common and most embarrassing portal bug: customer A seeing customer B's data by changing a number in the URL.

Rough timelines and costs

These are ranges, not quotes, and they assume a competent senior team and a client who answers questions within a day or two. Slow feedback is the single biggest schedule killer, more than any technical choice.

  • A focused phase-one portal (auth, two or three core screens, billing via Stripe, one data source): roughly 3 to 6 weeks. Think four-figure to low five-figure range depending on integrations.
  • A B2B portal with organizations, roles, invites, and SSO: roughly 6 to 12 weeks. SSO and per-role permissions are where the time goes, not the screens.
  • Deep integrations (live two-way sync with an ERP, custom reporting, document generation): add weeks per integration. Each external system has its own quirks, rate limits, and bad days.

The cost driver is rarely the UI. It is the number of external systems you touch and how messy their data is. A portal that reads from one clean Postgres database is cheap. A portal that reconciles three legacy systems with conflicting customer records is not, and most of that work is data plumbing nobody sees.

What to watch out for

The empty-state problem. New customers log in and see nothing, because they have no orders, no invoices, no history yet. An empty portal feels broken. Budget real design time for first-run states that explain what will appear here and what to do next.

Notifications that pull people back. A portal with no reason to return is a portal people forget. The thing that makes a portal sticky is usually an email or a text that says "your invoice is ready" or "your order shipped," with a link straight to the relevant screen. The notification is not a nice-to-have. It is the loop that keeps the portal alive.

Mobile reality. A large share of customers will open the link on a phone, from that notification email, standing in line somewhere. If your portal is a desktop-first dashboard that breaks on a 390px screen, they bounce. Build the core flows mobile-first.

Scope creep dressed as "while we're in there." Once a portal exists, every department wants their feature in it. Ship the focused version, watch what customers actually click (add basic analytics on day one so you are not guessing), and let real usage decide phase two. The features people request loudest are often not the ones they use.

Support as a feature, not an afterthought. Even the best portal will not answer everything. A visible, low-friction "contact us" path inside the portal keeps people from rage-quitting when their edge case is not covered. The goal is to shrink your support inbox, not pretend it will hit zero.

The takeaway

A customer portal earns its keep when logging in is faster than emailing you. Build the two or three screens that replace your most repetitive tickets, scope every data query to the logged-in user, lean on Stripe and a real auth provider instead of building your own, and add the notification loop that gives people a reason to come back. Start narrow, measure what gets used, and expand from evidence rather than wish lists.

If you want a second opinion on what your phase one should actually contain, we are happy to look at your support inbox with you.

Related service

SaaS Platforms

From first login to multi-tenant scale.

Learn more

Want this built right?

This is the work we do every day. Tell us what you are building and we will show you exactly how we would ship it.

hello@lazydevsagency.com