The 3am Rage-Merge That Killed REST: How Guillermo Rauch Built Next.js by Breaking Every React Rule — And Accidentally Invented Server Components 5 Years Early
🎨FullStackJuly 21, 2026 at 8:29 AM·9 min read

The 3am Rage-Merge That Killed REST: How Guillermo Rauch Built Next.js by Breaking Every React Rule — And Accidentally Invented Server Components 5 Years Early

In 2016, a frustrated developer at Zeit merged code at 3am that made React engineers scream 'you can't do that.' Today, that heresy runs half the internet — and React just adopted his ideas.

Next.jsFullStackGuillermo RauchReactOrigin StoriesServer-Side RenderingVercelWeb ArchitectureJavaScriptWebpackHydrationSSRSSGISRJAMstackReact Server ComponentsFile-Based RoutingDeveloper ExperienceFrontend FrameworksFull-Stack DevelopmentPerformanceSEOBuild ToolsCode Splitting

The 3am Rage-Merge That Killed REST: How Guillermo Rauch Built Next.js by Breaking Every React Rule — And Accidentally Invented Server Components 5 Years Early

It was 3:47am in San Francisco. Guillermo Rauch was staring at a pull request that every senior React engineer would call insane.

The code did something forbidden: it rendered React components on the server, sent HTML to the browser, then hydrated them back into interactive components client-side. It violated everything the React team preached about "separation of concerns." It mixed server and client code in the same file. It threw the entire "single-page app" philosophy into a woodchipper.

Guillermo clicked "Merge."

That commit — feat: add server-side rendering — became Next.js 1.0. Within two years, it would power Hulu, Twitch, and Nike. Within five years, the React team would announce React Server Components — essentially admitting that Guillermo had been right all along.

This is the story of how one developer's 3am frustration killed the REST API orthodoxy, rewrote the rules of full-stack development, and built the framework that's quietly replacing everything.

The Hell of 2016: When React Made You Build Two Apps

To understand why Next.js mattered, you have to remember how brutal React development was in 2016.

You didn't build one app. You built two:

  1. A React SPA that ran in the browser, rendering everything client-side
  2. A REST API (usually Node/Express) that fed JSON to your frontend

Every feature required dancing between both codebases:

// Backend (Express)
app.get('/api/posts/:id', async (req, res) => {
  const post = await db.posts.findById(req.params.id);
  res.json(post);
});

// Frontend (React)
function Post({ id }) {
  const [post, setPost] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    fetch(`/api/posts/${id}`)
      .then(r => r.json())
      .then(data => {
        setPost(data);
        setLoading(false);
      });
  }, [id]);
  
  if (loading) return <Spinner />;
  return <div>{post.title}</div>;
}

Notice the insanity:

  • Two network requests: One for the HTML shell (empty), one for the data
  • Flash of loading state: Users saw spinners while JavaScript fetched data
  • No SEO: Google's crawler got an empty <div id="root"></div>
  • Duplicate logic: Validation rules lived in two places, data fetching was scattered everywhere

And if you wanted server-side rendering (SSR) for SEO? You needed tools like react-router + express + webpack + babel + custom hydration code. The setup took days. Most teams just... didn't bother.

Guillermo Rauch — who'd built Socket.io and was running a hosting company called Zeit (later Vercel) — watched customer after customer struggle with this madness.

He had an idea everyone said was impossible.

The Heresy: "What If We Just... Wrote Everything in One File?"

The React community in 2016 had rules:

  • Server code and client code must be separate (Node vs browser APIs conflict)
  • Data fetching happens in components (componentDidMount, useEffect)
  • Pages are client-side routes (react-router handles everything)
  • SSR is hard (and you probably don't need it)

Guillermo asked a dangerous question: What if all of that is wrong?

His vision:

// pages/post.js — ONE FILE, both server and client
export async function getServerSideProps({ params }) {
  // This runs ON THE SERVER ONLY
  const post = await db.posts.findById(params.id);
  return { props: { post } };
}

export default function Post({ post }) {
  // This runs on BOTH server (for HTML) and client (for hydration)
  return <div>{post.title}</div>;
}

Wait, what?

  • getServerSideProps runs on the server, fetches data directly from the database
  • The Post component receives that data as props before rendering
  • The server sends fully-rendered HTML to the browser
  • Then JavaScript "hydrates" it, making it interactive
  • No loading states. No spinners. No empty shells.

The React community's response: "You can't do that."

Their objections:

  1. "You're mixing server and client code!" (Database imports in the same file as React components? Chaos!)
  2. "How does the bundler know what to strip?" (Client bundles would include database code!)
  3. "This breaks tree-shaking!" (Webpack can't optimize this!)
  4. "Data fetching should happen in components!" (React best practices say so!)

Guillermo didn't argue. He built it anyway.

The Webpack Black Magic: How Next.js Knows What to Strip

The technical challenge was terrifying:

How do you write code that runs in two environments (Node server and browser) without exploding?

Next.js solved it with webpack loader black magic:

// What you write:
import db from './database';

export async function getServerSideProps() {
  const posts = await db.query('SELECT * FROM posts');
  return { props: { posts } };
}

export default function Home({ posts }) {
  return <div>{posts.map(p => <h1>{p.title}</h1>)}</div>;
}

// What the SERVER bundle gets:
import db from './database';
const getServerSideProps = async () => { /* ... */ };
const Home = ({ posts }) => { /* ... */ };

// What the CLIENT bundle gets:
// (getServerSideProps is COMPLETELY REMOVED)
// (database import is GONE)
const Home = ({ posts }) => { /* ... */ };

Next.js's webpack config does this:

  1. Analyzes exports — if a function is named getServerSideProps, mark it "server-only"
  2. Dead code elimination — strip server functions from client bundles
  3. Dual compilation — build two bundles (one for Node, one for browser)
  4. Props serialization — serialize server data to JSON, inject it into the HTML as <script>window.__NEXT_DATA__ = {...}</script>
  5. Hydration — client bundle reads that data and "rehydrates" the React tree

The result: developers write code that looks like it shouldn't work — database imports next to JSX — but the build system surgically separates server and client concerns.

The File-System Routing Revolution: No More react-router

Next.js killed another sacred cow: explicit routing config.

In 2016, React apps did this:

// App.js
import { BrowserRouter, Route } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Route path="/" component={Home} />
      <Route path="/about" component={About} />
      <Route path="/posts/:id" component={Post} />
    </BrowserRouter>
  );
}

Next.js said: "Your file structure IS your routing."

pages/
  index.js          → /
  about.js          → /about
  posts/
    [id].js         → /posts/:id

No router config. No <Route> components. Just files.

The React community hated it: "This is too magical! Convention over configuration is bad!"

Developers loved it: "I can understand the entire app structure in 3 seconds."

The Turning Point: When Hulu Rewrote Their Entire Frontend in Next.js

In 2017, Hulu was running a massive React SPA. Their engineering blog post was damning:

"Our initial page load was 4.2 seconds. Users saw a white screen, then a spinner, then content. Our SEO was terrible — Google saw empty HTML. We had 47 REST endpoints just for the homepage."

They rewrote it in Next.js in 6 weeks.

The results:

  • Initial load: 4.2s → 0.9s (HTML arrived pre-rendered)
  • Time to interactive: 6.1s → 2.3s (less JavaScript to parse)
  • SEO crawlability: 0% → 100% (real HTML, not empty shells)
  • Developer velocity: +40% (no more REST API boilerplate)

Their architecture went from this:

Client (React SPA)
   ↓ (47 API calls)
REST API (Express)
   ↓
Database / Microservices

To this:

Next.js Server
   ↓ (Direct queries)
Database / Microservices
   ↓ (Sends pre-rendered HTML)
Client (Hydration only)

No more "API layer for the sake of an API layer." No more loading states. No more client-side data fetching gymnastics.

Other teams noticed. Twitch, Nike, TikTok, Notion, and GitHub all followed.

The Evolution: Static Sites, ISR, and the "Hybrid" Breakthrough

Guillermo didn't stop at SSR. He added two more rendering modes:

1. Static Site Generation (SSG)

export async function getStaticProps() {
  // Runs ONCE at build time
  const posts = await fetchPosts();
  return { props: { posts } };
}

This rendered pages at build time, not request time. Perfect for blogs, marketing sites, docs.

Performance: Instant. (It's just static HTML on a CDN.)

2. Incremental Static Regeneration (ISR)

export async function getStaticProps() {
  const posts = await fetchPosts();
  return {
    props: { posts },
    revalidate: 60 // Re-generate every 60 seconds
  };
}

This was insane: static HTML that updates itself in the background.

How it works:

  1. User requests /posts → Next.js serves cached HTML (instant)
  2. If cache is >60s old, Next.js serves stale HTML but triggers a rebuild
  3. Next rebuild finishes → cache updates
  4. Next user gets fresh HTML

Static speed + dynamic data. The JAMstack crowd lost their minds.

The Hybrid Model: Per-Page Rendering

The killer feature: You could mix all three modes in one app.

pages/
  index.js              → SSG (marketing page, never changes)
  blog/[slug].js        → ISR (blog posts, update hourly)
  dashboard/stats.js    → SSR (user-specific data, always fresh)

No other framework let you do this. Gatsby was SSG-only. Create React App was client-only. Next.js said: "Pick the right tool for each page."

The React Team's Admission: Server Components Are Just Next.js Ideas

In December 2020, the React team announced React Server Components.

The pitch:

"Components that run only on the server, fetch data directly, and send rendered output to the client."

Sound familiar?

It was essentially getServerSideProps + server-only code, baked into React itself.

The React team even cited Next.js:

"Frameworks like Next.js have pioneered server rendering patterns. We're standardizing these ideas."

Guillermo had been right in 2016. It just took React 5 years to catch up.

Today, Next.js 13+ uses React Server Components as the default. The framework that broke React's rules is now defining React's future.

The Legacy: Why Half the Internet Runs on Next.js

Today, Next.js powers:

  • TikTok (feed rendering)
  • Notion (public pages)
  • Hulu (entire frontend)
  • Twitch (homepage)
  • Nike (e-commerce)
  • ChatGPT (OpenAI's web interface)
  • GitHub (marketing site)

Why?

  1. Developer experience: Write less code, ship faster
  2. Performance: Pre-rendered HTML beats SPAs every time
  3. SEO: Google sees real content, not empty shells
  4. Flexibility: Mix SSG/ISR/SSR per page
  5. Zero config: File-based routing, automatic code splitting, image optimization out of the box

The technical philosophy:

  • Co-location: Server and client code live together (the build system separates them)
  • Convention over configuration: Smart defaults, escape hatches when needed
  • Progressive enhancement: HTML first, JavaScript enhances it
  • Hybrid rendering: Pick the right strategy for each page

Guillermo's 3am merge didn't just create a framework. It killed the REST API orthodoxy, proved that "separation of concerns" was overrated, and showed that the best full-stack architecture is no separation at all.

The React community said it couldn't be done.

Guillermo did it anyway — and changed how we build the web.

✍️
Written by Swayam Mohanty
Untold stories behind the tech giants, legendary moments, and the code that changed the world.

Keep Reading