Skip to content

How to Check if Your React App is SSR or CSR

I recently saw a discussion about developers who don’t know whether their React app uses server-side rendering (SSR) or client-side rendering (CSR). This confusion leads to problems with caching, SEO, and performance. I wanted to write a practical guide on how to detect your app’s rendering mode.

Why This Matters

When you use frameworks like Next.js, the rendering complexity is abstracted away. You might assume your app is SSR because you use Next.js, but you may have accidentally created a CSR app by adding 'use client' at the root level.

This misunderstanding affects:

  • Caching strategy: Browser cache vs CDN cache vs no cache
  • SEO: Crawlable content vs empty initial HTML
  • Performance: Time to First Byte vs Time to Interactive

Quick Answer

The simplest check is:

ssr-check.js
if (typeof window === 'undefined') {
console.log('Running on server (SSR)');
} else {
console.log('Running in browser (CSR)');
}

On the server, window does not exist. In the browser, it does. This is the core distinction between SSR and CSR.

Method 1: Check the Build Output

After building your app, look at the output files:

  • Static HTML files with full content = SSG (Static Site Generation) or SSR
  • Empty index.html with a root div = CSR

For a Next.js app, check the .next/server folder. If you see HTML files with your actual content, the app uses SSR.

Method 2: View Page Source in Browser

Open your deployed app and right-click “View Page Source”:

  • SSR: You see the full HTML content with text, images, and links
  • CSR: You see a minimal HTML shell with script tags and an empty root div
csr-example.html
<!DOCTYPE html>
<html>
<head>
<script src="/bundle.js"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>

If your page source looks like this with no actual content, you have a CSR app.

Method 3: React Hook for Client Detection

I created a reusable hook to detect client-side rendering:

useIsClient.js
import { useState, useEffect } from 'react';
export function useIsClient() {
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
}, []);
return isClient;
}

Use it in your components:

MyComponent.jsx
import { useIsClient } from './useIsClient';
function MyComponent() {
const isClient = useIsClient();
if (!isClient) {
return <div>Loading...</div>;
}
return <div>Client-side content: {window.location.href}</div>;
}

This pattern is useful when you need to access browser-only APIs like window or localStorage.

Method 4: Next.js App Router Indicators

In Next.js 13+ with App Router, check for the 'use client' directive:

ClientComponent.jsx
'use client'; // This runs on the client
import { useState } from 'react';
export default function ClientComponent() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
ServerComponent.jsx
// No 'use client' = Server Component (default in App Router)
export default function ServerComponent() {
// This code runs on the server only
// You cannot use useState, useEffect, or event handlers here
return <div>Server-rendered content</div>;
}

Server Components are the default in Next.js App Router. Only files with 'use client' at the top run in the browser.

Method 5: Check Network Tab

Open DevTools and go to the Network tab. Reload the page and look at the first HTML document:

  • SSR: The HTML document response contains your page content
  • CSR: The HTML document is small, followed by JavaScript chunks that render the content

Common Mistakes

I see these mistakes often:

Mistake 1: Adding 'use client' everywhere

WrongExample.jsx
'use client'; // This defeats SSR benefits!
// A simple component that doesn't need client-side JS
export default function StaticText() {
return <p>This is just text.</p>;
}

Only add 'use client' when you need browser features like useState, useEffect, or event handlers.

Mistake 2: Using useEffect for server-fetchable data

InefficientDataFetching.jsx
'use client';
import { useState, useEffect } from 'react';
export default function ProductList() {
const [products, setProducts] = useState([]);
useEffect(() => {
// This fetches on the client, causing a loading state
fetch('/api/products')
.then(res => res.json())
.then(setProducts);
}, []);
if (products.length === 0) return <div>Loading...</div>;
return <ul>{products.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
}

Instead, fetch on the server:

ServerDataFetching.jsx
// Server Component - no 'use client'
async function ProductList() {
const res = await fetch('/api/products');
const products = await res.json();
return (
<ul>
{products.map(p => <li key={p.id}>{p.name}</li>)}
</ul>
);
}

Mistake 3: Assuming Next.js = automatic SSR

Next.js App Router uses Server Components by default. But Pages Router (older) requires getServerSideProps for SSR. Know your router version.

Quick Reference Table

Rendering Mode Reference
| Check | SSR | CSR |
|-------|-----|-----|
| typeof window === 'undefined' | true | false |
| View page source | Full HTML | Empty shell |
| Next.js App Router | No 'use client' | Has 'use client' |
| Build output | HTML files | Bundle files only |
| First paint | Content visible | Loading state |

Summary

In this post, I showed you five ways to detect if your React app uses SSR or CSR:

  1. Check typeof window === 'undefined'
  2. Examine build output files
  3. View page source in browser
  4. Use the useIsClient hook for runtime detection
  5. Look for 'use client' directives in Next.js

The key insight is that SSR runs on the server where window doesn’t exist, while CSR runs in the browser where it does. Understanding this distinction helps you make better decisions about caching, SEO, and performance.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments