Server-Side Rendering (SSR) & Data Fetching

One of the most potent advantages of Next.js over vanilla React is the ability to securely fetch data on a Node.js server BEFORE sending the HTML down to the user's browser. This is crucial for SEO, performance, and keeping sensitive API keys hidden.

When a page uses Server-Side Rendering (SSR), the HTML is generated fresh on the server for each and every request. This is beneficial for highly dynamic data, like user-specific dashboards or constantly updating stocks.

SSR in the App Router (Next 13+)

The modern Next.js App Router defaults to using React Server Components. This fundamentally alters data fetching. You can now use standard async / await directly inside the component without any special Next-specific API functions.

// app/products/page.js
// By default, this is a Server Component.

// Data Fetching logic
async function getProducts() {
  const res = await fetch('https://api.example.com/products', {
    cache: 'no-store' // This ensures SSR behavior (fetch on every request)
  });
  return res.json();
}

// The Component itself is async!
export default async function ProductsPage() {
  // We await the data before rendering the UI
  const products = await getProducts();

  return (
    <div>
      <h1>Available Products</h1>
      <ul>
        {products.map(product => (
          <li key={product.id}>{product.name} - ${product.price}</li>
        ))}
      </ul>
    </div>
  );
}

No more useEffect or loading spinners—the HTML is delivered pre-populated with data.

SSR in the Pages Router (`getServerSideProps`)

If your project uses the classical Pages Router, you perform SSR by exporting an asynchronous function called getServerSideProps from your page.

// pages/products.js

export default function ProductsPage({ products }) {
  // Receives 'products' as a prop rendered by the server!
  return (
    <div>
      <h1>Available Products</h1>
      <ul>
        {products.map(product => (
          <li key={product.id}>{product.name}</li>
        ))}
      </ul>
    </div>
  );
}

// Next.js recognizes this export and runs it on the Node.js server
export async function getServerSideProps(context) {
  // Request context (req, res, query params) is available here!
  const res = await fetch('https://api.example.com/products');
  const data = await res.json();

  // Pass data to the page via props
  return {
    props: {
      products: data
    }
  };
}

Why use SSR over standard React Client fetches?

  • SEO Optimization: Search engine bots easily parse the pre-rendered HTML without executing JavaScript.
  • Security: Direct database queries or API fetches requiring Secret Keys can be ran safely on the Node backend without exposing keys to the client payload.
  • Performance: It reduces the client's processing load and often resolves data "waterfalls" by shifting the load to backend servers closer to the database.

When NOT to use SSR

Because SSR constructs the HTML anew on every single request, it demands server computational power. This can slow down Time To First Byte (TTFB) compared to static files. If your data rarely changes (e.g., a simple marketing blog), you should use Static Site Generation (SSG), which we'll cover in the next section.