Skip to main content

Command Palette

Search for a command to run...

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

Updated
6 min read
How React Really Works (And What Next.js Adds on Top)
A

Hi, I am Adyasha Mohanty, a self taught developer extraordinaire from India. I am passionate about bringing ideas to life from the ground up whether that’s crafting beautiful or building intuitive user interfaces. Beyond coding, I thrive on sharing knowledge, engaging with the tech community and helping others grow. I love the entire journey of creation from the first line of code to shipping polished, user ready products. Let’s build, learn and ship amazing things together. 🚀

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:

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:

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:

{
  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:

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:

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:

<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:

<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

ModeAPI FunctionRuns OnUse Case
SSRgetServerSidePropsEvery requestPersonalised content
SSGgetStaticPropsAt build timeBlogs, marketing pages
ISRrevalidate key in getStaticPropsOn demandNews 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 :)