Next.js Server vs Client: What Runs Where, and What Must Stay Secure
A practical guide to Server Components, Client Components, Route Handlers, and Server Actions in Next.js. Learn what belongs on the server, what belongs in the browser, and why network requests are always visible to users.
Next.js Server vs Client: What Runs Where, and What Must Stay Secure
If you build with Next.js, one of the most important design decisions is: what runs on the server and what runs in the browser.
This is not just a performance question. It is also a security question.
A common misunderstanding is: “If we keep everything on the server, users cannot track network requests.”
That statement is not fully correct.
The Security Reality First
Users can always inspect requests made from their browser (DevTools, proxies, extensions, etc.).
So the goal is not to hide every request. The goal is to ensure that even if requests are visible (or replayed/modified), sensitive operations are still protected by server-side controls:
- authentication
- authorization
- server-side validation
- rate limiting
- business rule enforcement
Think of it this way: visibility is normal; trust is the real problem.
Next.js Rendering Model in Simple Terms
Next.js gives you two component types:
- Server Components (default)
- Client Components (
"use client")
It also gives server runtime entry points:
- Route Handlers (
app/api/.../route.ts) - Server Actions (
"use server")
Understanding these boundaries helps you design safer systems.
Server Components
Server Components render on the server and send HTML/serialized payload to the browser.
Good for
- data fetching from internal/private backends
- reading secrets from environment variables
- secure pre-rendered views
- reducing client bundle size
Security advantage
Secrets used only in Server Components are not shipped to client JS bundles.
Important limitation
Even if initial data comes from server rendering, later user interactions may still trigger network activity that users can inspect.
Client Components
Client Components run in the browser after hydration.
Good for
- interactivity (
useState,useEffect, events) - local UI state
- browser-only APIs (storage, media, window)
Security limitation
Everything in Client Components is user-controlled context:
- code can be inspected
- requests can be observed
- payloads can be modified and replayed
Never place security decisions exclusively in client code.
Route Handlers (Backend Endpoints in Next)
Route Handlers are server endpoints where you should enforce security.
Use them for:
- privileged operations
- token/cookie verification
- server-side data validation
- input normalization and policy checks
If a request is sensitive, assume attacker controls input completely and validate accordingly.
Server Actions
Server Actions let you call server-side functions from forms/components.
They can reduce boilerplate, but they do not replace security checks.
You still need:
- auth checks in action body
- authorization checks per resource
- schema validation
- anti-abuse controls
Treat Server Actions like any mutation endpoint.
“Can Users Track Network Requests?”
Short answer
From browser-based flows: yes.
What you can do instead
- avoid exposing secrets in frontend code
- keep critical logic server-side
- sign and verify sensitive state
- reject tampered client input
- return minimal data needed by UI
Security comes from verification, not obscurity.
Practical Placement Guide
Put this on the server
- API keys, private credentials, signing secrets
- pricing/discount/business-critical calculations
- role/permission checks
- order/payment finalization
- trust decisions (who can do what)
Put this on the client
- presentation logic
- optimistic UI states
- non-sensitive form UX
- local interactions and animation
Client should express intent. Server should decide outcome.
Example: Checkout Total (Classic Mistake)
Bad pattern:
- client computes
total - server charges exactly that total
Secure pattern:
- client sends cart items/coupon intent
- server recomputes final amount from trusted data
- server returns payable amount / creates payment intent
Even if attacker modifies request payload, server-side recomputation prevents fraud.
Common Anti-Patterns to Avoid
- trusting hidden form fields as secure inputs
- enforcing authorization only in UI guards
- using client-side validation as the only validation
- exposing tokens/secrets in
NEXT_PUBLIC_* - assuming HTTPS alone prevents request tampering attempts
HTTPS protects transport confidentiality/integrity in transit, not business logic abuse.
Recommended Security Checklist for Next.js Apps
- Every sensitive mutation is enforced server-side
- Object-level authorization checks exist on each protected resource
- Input validation runs on server (schema + business rules)
- Secrets are never exposed through client bundles
- Rate limiting and monitoring are enabled for critical endpoints
- Logs include actor, action, target, and decision outcome
- Tests include negative scenarios (tampered/replayed requests)
Performance + Security Can Coexist
Using Server Components does not mean giving up interactivity.
A practical pattern:
- Server-render initial data where possible.
- Hydrate focused Client Components for UX.
- Send all sensitive writes through secured server endpoints/actions.
This gives good performance and good security boundaries.
Final Takeaway
Next.js gives powerful server/client tools, but architecture choices matter.
- Use Server Components and server runtime features to protect secrets and centralize trust decisions.
- Use Client Components for UX and interaction.
- Accept that users can inspect browser network traffic.
Your job is not to hide requests. Your job is to ensure requests cannot bypass security or business rules.
Rate this post
All fields are optional. Just stars is fine.