# How React Really Works (And What Next.js Adds on Top)

## Intro ✨

Okay, full disclosure: I had no plans to write this. I was *supposed* to be working on a blog about my solo trip (which is still just a folder of photos, honestly) but instead, I ended up explaining React and Next.js to my cousin over the weekend. He was super curious about how stuff like `useState` and server-side rendering actually works. I grabbed a pen, sketched things out and it clicked for him. That moment that “aha!” made me think, hey, maybe this could help others *too.*

So this blog is more of a brain dump. If you’re someone who knows React but wants to understand *why* things work the way they do, this is for you.

---

## What Happens When You Write React Code?

Let’s start with what you actually write:

```javascript
function App() {
  return <h1>Hello, World!</h1>;
}
```

It might feel like HTML inside JavaScript but this is actually JSX. And JSX isn’t something the browser understands directly, it gets converted by a compiler (like Babel) to a function call:

```javascript
React.createElement('h1', null, 'Hello, World!');
```

That call creates a **React element**, not an actual DOM node basically just a plain JavaScript object that describes what you want on screen:

```javascript
{
  type: 'h1',
  props: {
    children: 'Hello, World!'
  },
  key: null,
  ref: null
}
```

This is React's **Virtual DOM**, a lightweight, in memory representation of your UI. React holds onto this to later figure out what needs to be updated in the actual DOM.

---

## What Actually Happens When a Component Renders?

Consider:

```javascript
function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
```

Here's what happens:

1. React renders the component, calling it like a function.
    
2. It sets up internal tracking via **hooks** by an internal "dispatcher".
    
3. When `useState` runs, it checks if it's the first render or a re-render and either initializes state or returns the latest.
    
4. When you click the button, `setCount` triggers a re-render.
    
5. React compares the new return value with the previous one and applies only the changed parts (we will talk about that in reconciliation).
    

### Class Components:

```javascript
class Counter extends React.Component {
  state = { count: 0 };
  
  increment = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return <button onClick={this.increment}>{this.state.count}</button>;
  }
}
```

Same idea, more boilerplate. Internally, React assigns an **updater** to manage `setState`, which schedules a re-render.

> Class components give you lifecycle methods but they have largely been replaced by `useEffect`, `useLayoutEffect` etc in modern apps.

---

## Reconciliation: The Diff That Keeps Things Fast

This is where things get fun. Reconciliation is React’s algorithm for updating the UI efficiently.

### Why does this matter?

DOM operations are expensive. So rather than refreshing the whole page (like jQuery used to do), React compares what’s changed and only touches those parts.

### How it works:

1. A component’s state changes.
    
2. React re-renders that component and builds a new tree of React elements.
    
3. It compares this new tree with the old one.
    
4. React calculates the **minimum number of operations** to apply to the DOM.
    
5. Only the changed nodes are updated.
    

This process is called **diffing** and it’s fast O(n) because React uses some smart assumptions:

* Elements with the same type get updated.
    
* Keys help identify elements in a list.
    
* If keys or types change, React assumes it’s a new element.
    

### Real example:

```javascript
<ul>
  {items.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
```

With stable keys, React knows which list items were removed, moved or added.

This optimization is why React apps feel snappy.

---

## The Rendering Pipeline in React

Here’s how rendering flows:

1. JSX turns into React elements
    
2. React creates or updates the Fiber tree
    
3. Fiber nodes map to real DOM nodes via `react-dom`
    
4. DOM updates are batched and applied
    

React renders in two phases:

* **Render phase:** pure, no DOM mutations
    
* **Commit phase:** applies DOM changes, runs effects
    

The Fiber architecture enables React to pause and resume rendering, key to features like Suspense and Concurrent Mode.

---

## Where Next.js Comes In

React doesn’t care where it renders. Browser, native, terminal? It just gives you the virtual DOM.

But when you want:

* SEO
    
* Server rendered pages
    
* Static pages with dynamic data
    

...you need more. That’s where **Next.js** shines.

### What Next.js Adds

* Server-side rendering (SSR)
    
* Static site generation (SSG)
    
* File-based routing
    
* Built-in API routes
    
* Image optimization
    

It’s not just a framework — it’s an opinionated toolkit that works *with* React to make your life easier.

### Traditional SPA (React only)

1. Server returns a mostly empty HTML shell
    
2. JS bundle loads
    
3. React mounts and fetches data
    
4. UI finally appears
    

### With Next.js (SSR)

1. Server renders full HTML page with data
    
2. User sees meaningful content immediately
    
3. React hydrates it in the browser
    

---

## What’s Hydration?

Let’s say you server render a page. The user gets full HTML: content, structure, everything.

But it’s not interactive yet.

**Hydration** is the process of attaching React’s logic i.e event listeners, hooks, state to that HTML. It turns your static HTML into a live React app in the browser without re-rendering the DOM.

This is possible because Next.js sends a JSON blob:

```javascript
<script id="__NEXT_DATA__">{ props, paths, page info }</script>
```

This contains the props returned by `getServerSideProps` or `getStaticProps`, letting React "catch up" with what’s already on screen.

> ⚠️ If hydration fails (due to mismatch), React logs warnings and may re-render.

---

## Things You Can't Do on the Server

Server rendering has limits. You are in a Node.js environment, not a browser. So:

* No `window`, `document`, `localStorage`
    
* No user event handlers
    
* No CSS media query logic
    
* No DOM measurements (like `getBoundingClientRect()`)
    

> If you need those, defer them to `useEffect()` which only runs on the client.

---

## Next.js Rendering Modes

| Mode | API Function | Runs On | Use Case |
| --- | --- | --- | --- |
| SSR | `getServerSideProps` | Every request | Personalised content |
| SSG | `getStaticProps` | At build time | Blogs, marketing pages |
| ISR | `revalidate` key in `getStaticProps` | On demand | News feeds, product updates |

This flexibility lets you choose performance or freshness depending on your page needs.

---

## Final Thoughts

React gives you the tools to build UIs efficiently with declarative, component-driven code. It abstracts the pain of DOM manipulation and makes state management predictable.

But when you need a full-fledged web app with performance, SEO and server integration, you’ll want a framework like Next.js.

Next.js doesn’t replace React. It completes it.

---

Want to turn this into a series or go even deeper into Suspense, useTransition or how the React scheduler works? Let me know.

Also yes, that solo trip blog is still coming. Probably. Eventually :)
