Next.js is a React web framework with both browser-facing and server-side capabilities. The useful question is where a particular piece of your application executes: in the browser, during a build, during a server request, or in a separate worker. That location determines which data it can access and what happens when a process stops.

Consider a customer portal with a public catalogue, a private account page and an export button. All three can share a Next.js project, but they should not share an undifferentiated “backend” box. This guide uses that hypothetical portal to map the boundaries. It focuses on the App Router; an older Pages Router project needs its own migration inventory.

Map each responsibility before choosing another server

Where the portal does its work
ResponsibilityExecution contextBoundary to verify
Interactive filters and button stateBrowser, through Client ComponentsSend only data the signed-in person may see.
Read data for an initial pageServer Component, at build or request time as configuredCheck whether the data is public, user-specific or stale.
Answer an HTTP API requestRoute Handler on a supported server runtimeValidate input and enforce resource authorization.
Change a customer recordAuthorized server operationValidate the transition and handle concurrent writes.
Generate a long exportDurable background-work mechanismPersist the request and expose progress/recovery.

This division does not require five independently deployed services. A small application can keep several responsibilities in one codebase. It does require a clear owner for state, permissions and failures. Our Node.js versus Next.js guide explains the separate runtime-versus-framework distinction.

Server and Client Components do different jobs

The Server and Client Components documentation describes how server-rendered output and browser interactivity fit together. In the App Router, pages and layouts are Server Components by default. Use a client boundary for state, event handlers and browser APIs; keep it close to the interactive portion rather than automatically making the whole page client-side.

'use client' is not a promise that a component never participates in server prerendering. On an initial load, HTML can provide a noninteractive preview before browser JavaScript hydrates the interactive components. A browser-only API such as window therefore needs a browser-safe execution point.

For the catalogue, the server can prepare the initial item data while a Client Component owns a filter control. For the private account page, the server must resolve the authenticated user and select only permitted fields. Passing a database object wholesale into the client is a data decision, not just a rendering shortcut.

A Route Handler is a reachable endpoint

A file such as app/api/items/[id]/route.js defines an HTTP endpoint. It is not private merely because its source file lives on the server. Next.js's backend-for-frontend guide explains that these endpoints can serve different clients and content types.

For public catalogue data, an unauthenticated read can be intentional. For customer orders, the handler needs authentication and a resource-level permission check. A valid session does not mean the user may request any order ID. The service handling the read should receive the authorized context and return a deliberately small response.

Do not send a Server Component through your own public HTTP endpoint just to reach code it could call directly on the server. That adds another request boundary. Share an appropriate server-side data function when it fits; retain a public endpoint when a browser, mobile app or external integration actually needs that contract.

Keep server code and secret data separate from client output

The Next.js data-security guidance recommends deliberate data-access boundaries. Keep privileged credentials and database access in server-only code. Decide which fields may cross into rendered HTML, serialized component data or JSON responses. A server-side function can still disclose private information through what it returns.

For a customer summary, define an output such as display name and current plan rather than exposing internal notes or authentication fields. If an action changes a record, perform authorization when it runs, using current permissions. Hiding an edit button improves the interface but does not establish that a forged request is denied.

Also review the cache boundary. A response containing one customer's account information should not become another customer's cached response. Record whether each result is public, private or intentionally uncached, then test with two accounts and the actual deployment cache configuration.

Build-time work is not request-time work

A public information page can often be generated before anyone requests it. A personalized account page depends on the current request. Those are different requirements even when both produce HTML. Write down when data is read, how long it may remain stale and what invalidates it.

A static export cannot provide arbitrary request-time server behavior. Features such as request-dependent handlers and cookies, Server Actions and server rewrites need a compatible runtime rather than only exported files. Some build-time GET output can be exported; that does not turn a static host into a running API server.

Before deploying the portal, ask the host-specific questions: how long may a request run, is local storage durable, how are secrets supplied, and which process handles jobs? Framework capability and a provider's execution limits are different inputs. Do not discover that distinction after an export starts timing out.

Separate a request from a durable operation

Suppose the export button creates a file from many customer records. The request can authorize the user, record an export operation and return its identifier. A worker can perform the work and store the result. A status endpoint then reports the operation's current state and a protected download location.

The failure cases matter: the worker may stop, a dependency may time out after accepting a request, or the browser may retry. Define attempt identity and recovery so a repeat does not blindly duplicate side effects. Moving the button into Next.js does not remove those responsibilities, and adding Express or NestJS does not automatically fulfill them.

Keep an operation in the request when its duration and failure behavior genuinely fit the request lifecycle. Move it when durability, independent retries or separate capacity requires that. The decision should name the requirement, not a general preference for more services.

Verify the boundary with one concrete request

Our companion Next.js versus Express API guide includes a tested public-catalogue example. Real local servers returned the expected JSON and status for success, invalid ID, missing item and a simulated data-source failure. It is a narrow HTTP contract check, not a test of the private portal described here.

For your portal, add the private cases: guest denied, another customer's ID denied, permitted fields only, stale cache avoided and duplicate export requests reconciled. The API evaluation example and migration worksheet records those checks separately from the public sample.

If the outstanding question is a structured service shared by several clients, continue with Next.js versus NestJS. For an existing application, our Next.js development service starts with the actual screen, request and failure you need to change.