How to Optimize Your Next.js 15 App for Better Performance


Prefer using
Server Components
whenever possible.Because server components reduce the amount of JavaScript sent to the client, leading to faster load times and better performance.
They are ideal for data-fetching, rendering static/dynamic content, and skipping unnecessary client-side hydration.
Mark a component as
client component
only whenIt involves human interaction with the UI (e.g., forms, buttons, dropdowns).
It exists at the edge of the component tree where interactivity is required.
The below snippet is build to create a new organization but here the static code I’ve rendered on the server.
2. And the part which is interacting with the human that only I have rendered on the client component.
If we wrap a
Server Component
inside aClient Component
, theServer Component
is automatically treated as aClient Component
.Server Components cannot be rendered inside Client Components without being treated as Client Components themselves.
This happens because the Client Component boundary forces hydration and browser execution, making everything inside it behave like client-side code.
This can negate performance benefits of Server Components, so it's best to avoid wrapping Server Components inside Client Components unless necessary.
Here my parent component is Client component as I marked it as
use client
and my second component it a server component because I have not marked it as “use client”
"In this example, the parent component is marked as a Client Component using
'use client'
, while the child component is technically a Server Component since it lacks the'use client'
directive.
However, because the child component includes anonClick
handler (a browser event), it must also behave as a Client Component. This is because Server Components cannot handle browser interactions like click events.
So even if you don't explicitly mark the child as a Client Component, using it inside a Client Component and including interactivity causes it to be treated as a Client Component anyway.Next.js pre-renders the
page.tsx
component on the server to generate an HTML bundle.
If you import a Client Component into a Server Component (such as apage.tsx
file), the JavaScript for that Client Component is included in the bundle, but it does not run on the server. Instead, it is hydrated and executed on the client side after the initial HTML is delivered.
Server-Side Data Fetching
GET
vs Data MutationCREATE
,UPDATE
,DELET
GET
requests (e.g.,fetch()
or DB queries) can run inside a Server Component.POST
,PUT
, andDELETE
operations should not happen directly inside Server Components — use Server Actions or route handlers for those.When you call a Server Action, Next.js automatically creates a
POST
request to the route where the Server Action is defined.Always authenticate the current user inside the Server Action before performing any sensitive operations.
This ensures that only authorized users can mutate data.
Parallel Data Fetching in
Server Components
Promise.allSettled()
ensures all promises complete, even if some fail.You can then check which ones succeeded or failed manually.
Utility Functions in Next.js 15
The main purpose of utility functions is to encapsulate logic like
GET
requests, abstracting away fetch logic from components.These functions can run on both the server and client, depending on where they are imported and used.
If you want a utility function to run only on the server, use the
"server-only"
directive provided by theserver-only
package.npm install server-only
Loading States in Next.js 15
Route-Level Loading with
loading.tsx
When navigating between two Server Components (routes), there may be a delay while the data for the destination route loads.
You can create a special file named
loading.tsx
next to your destinationpage.tsx
.Next.js will automatically render
loading.tsx
during the transition, until the new page's data is ready.app/ └─ dashboard/ ├─ page.tsx ← Server Component for /dashboard └─ loading.tsx ← Shown while /dashboard loads
Conclusion
Optimizing a Next.js 15 app involves making smart choices — using Server Components for performance, validating and authenticating Server Actions, parallelizing independent API calls, managing loading states effectively, and keeping server-only logic secure.
These best practices not only improve performance but also lead to more maintainable, scalable applications.
🙌 Let’s Connect
If you found this article helpful or have questions, feel free to reach out or drop your thoughts in the comments!
Subscribe to my newsletter
Read articles from Saurabh Manikeri directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
