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

  1. Prefer using Server Components whenever possible.

    1. Because server components reduce the amount of JavaScript sent to the client, leading to faster load times and better performance.

    2. They are ideal for data-fetching, rendering static/dynamic content, and skipping unnecessary client-side hydration.

  2. Mark a component as client component only when

    1. It involves human interaction with the UI (e.g., forms, buttons, dropdowns).

    2. It exists at the edge of the component tree where interactivity is required.

      1. 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.

  3. If we wrap a Server Component inside a Client Component, the Server Component is automatically treated as a Client Component.

    1. Server Components cannot be rendered inside Client Components without being treated as Client Components themselves.

    2. This happens because the Client Component boundary forces hydration and browser execution, making everything inside it behave like client-side code.

    3. This can negate performance benefits of Server Components, so it's best to avoid wrapping Server Components inside Client Components unless necessary.

      1. Here my parent component is Client component as I marked it as use client

      2. and my second component it a server component because I have not marked it as “use client”

        1. "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 an onClick 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.

        2. 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 a page.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.

  1. Server-Side Data Fetching GET vs Data Mutation CREATE, UPDATE, DELET

    1. GET requests (e.g., fetch() or DB queries) can run inside a Server Component.

      1. POST, PUT, and DELETE operations should not happen directly inside Server Components — use Server Actions or route handlers for those.

        1. When you call a Server Action, Next.js automatically creates a POST request to the route where the Server Action is defined.

        2. Always authenticate the current user inside the Server Action before performing any sensitive operations.

        3. This ensures that only authorized users can mutate data.

    2. Parallel Data Fetching in Server Components

      1. Promise.allSettled() ensures all promises complete, even if some fail.

      2. You can then check which ones succeeded or failed manually.

  2. Utility Functions in Next.js 15

    1. The main purpose of utility functions is to encapsulate logic like GET requests, abstracting away fetch logic from components.

    2. These functions can run on both the server and client, depending on where they are imported and used.

    3. If you want a utility function to run only on the server, use the "server-only" directive provided by the server-only package.

       npm install server-only
      

  3. Loading States in Next.js 15

    1. Route-Level Loading with loading.tsx

      1. When navigating between two Server Components (routes), there may be a delay while the data for the destination route loads.

      2. You can create a special file named loading.tsx next to your destination page.tsx.

      3. 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!

🔗 LinkedIn
💻 GitHub

2
Subscribe to my newsletter

Read articles from Saurabh Manikeri directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Saurabh Manikeri
Saurabh Manikeri