Full Circle: From PHP to Server Components

SS Saurav Sitaula

After learning React Server Components, I had a strange realization: we've come back to where we started. Server-side rendering, SQL in templates, zero client JavaScript—it sounds like PHP circa 2005. But is it really the same? A reflection on 20 years of web development coming full circle.

The Déjà Vu Moment

After diving deep into React Server Components, I was showing my code to a senior developer who’d been building websites since the early 2000s.

He looked at my Next.js App Router code:

async function ProductPage({ params }) {
  const product = await db.products.findOne({ id: params.id });
  const reviews = await db.reviews.find({ productId: params.id });
  
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <ReviewList reviews={reviews} />
    </div>
  );
}

He laughed. “You know what this reminds me of?”

He pulled up a PHP file from 2006:

<?php
$product = $db->query("SELECT * FROM products WHERE id = ?", [$_GET['id']]);
$reviews = $db->query("SELECT * FROM reviews WHERE product_id = ?", [$_GET['id']]);
?>

<div>
  <h1><?= $product['name'] ?></h1>
  <p><?= $product['description'] ?></p>
  <?php include 'components/review-list.php'; ?>
</div>

I stared at both snippets. He was right. They were… basically the same thing.

What have we been doing for the last 15 years?

The Journey Away from the Server

To understand where we are, we need to understand where we’ve been.

The PHP Era (2000-2010)

In the beginning, there was server-side rendering. PHP, ASP, JSP, Ruby on Rails—they all worked the same way:

  1. Request comes in
  2. Server fetches data from database
  3. Server renders HTML with that data
  4. Server sends HTML to browser
  5. Browser displays the page
// This was it. This was web development.
<?php
$posts = $db->query("SELECT * FROM posts ORDER BY created_at DESC");
foreach ($posts as $post) {
    echo "<article>";
    echo "<h2>" . htmlspecialchars($post['title']) . "</h2>";
    echo "<p>" . htmlspecialchars($post['content']) . "</p>";
    echo "</article>";
}
?>

Pros:

  • Simple mental model
  • Fast initial page load
  • SEO worked out of the box
  • No JavaScript required

Cons:

  • Full page reloads for every interaction
  • Limited interactivity
  • “Flash of white” between pages
  • Server did ALL the work

The jQuery Era (2006-2013)

Then jQuery came along. We could update parts of the page without reloading:

$('#load-more').click(function() {
  $.ajax({
    url: '/api/posts?page=2',
    success: function(data) {
      $('#posts').append(data);
    }
  });
});

We started moving logic to the client. But it was messy—jQuery spaghetti code, mixing HTML strings in JavaScript, no structure.

The SPA Revolution (2013-2020)

React, Angular, and Vue changed everything. The entire application ran in the browser:

function ProductPage({ id }) {
  const [product, setProduct] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    fetch(`/api/products/${id}`)
      .then(res => res.json())
      .then(data => {
        setProduct(data);
        setLoading(false);
      });
  }, [id]);
  
  if (loading) return <Spinner />;
  return <ProductDisplay product={product} />;
}

The promise:

  • Rich, app-like experiences
  • No page reloads
  • Smooth transitions
  • Interactive UIs

The reality:

  • Loading spinners everywhere
  • Huge JavaScript bundles
  • SEO nightmares
  • Complex state management
  • Slow initial loads
  • Content layout shift

We’d moved ALL the rendering to the client. The server just sent JSON.

The SSR Comeback (2016-2020)

We realized pure client-side rendering had problems. So we brought server rendering back… but kept the client-side framework:

// Next.js getServerSideProps
export async function getServerSideProps({ params }) {
  const product = await fetchProduct(params.id);
  return { props: { product } };
}

function ProductPage({ product }) {
  return <ProductDisplay product={product} />;
}

Better! But now we had:

  • Server renders HTML
  • Client downloads JavaScript
  • Client “hydrates” (re-renders everything)
  • Now the app is interactive

We were rendering everything twice. Server sends HTML, then client re-runs all that code to make it interactive.

And Now… Server Components

Which brings us to 2022 and React Server Components:

// This runs on the server. Only on the server.
async function ProductPage({ params }) {
  const product = await db.products.findOne({ id: params.id });
  
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <AddToCartButton productId={product.id} />  {/* Client component */}
    </div>
  );
}

Wait. This is:

  • Server fetches data directly from database ✓
  • Server renders HTML ✓
  • No client-side JavaScript for static parts ✓
  • Only interactive bits run on client ✓

This is PHP with better ergonomics.

The Comparison That Broke My Brain

Let me put them side by side:

Data Fetching

PHP (2005):

<?php
$user = $db->query("SELECT * FROM users WHERE id = ?", [$user_id])->fetch();
?>
<h1>Welcome, <?= htmlspecialchars($user['name']) ?></h1>

React Server Component (2022):

async function Welcome({ userId }) {
  const user = await db.users.findOne({ id: userId });
  return <h1>Welcome, {user.name}</h1>;
}

Almost identical. Both:

  • Run on the server
  • Query the database directly
  • Render HTML
  • Send zero JavaScript for this component

Including Components

PHP (2005):

<div class="sidebar">
  <?php include 'components/user-profile.php'; ?>
  <?php include 'components/recent-posts.php'; ?>
</div>

React Server Component (2022):

function Sidebar() {
  return (
    <div className="sidebar">
      <UserProfile />
      <RecentPosts />
    </div>
  );
}

Mixing Server and Client Logic

PHP (2005):

<div>
  <h1><?= $product['name'] ?></h1>
  
  <!-- Server-rendered -->
  <p class="price">$<?= $product['price'] ?></p>
  
  <!-- Client-side interactivity -->
  <script>
    document.getElementById('add-to-cart').addEventListener('click', function() {
      // AJAX call to add to cart
    });
  </script>
  <button id="add-to-cart">Add to Cart</button>
</div>

React Server Component (2022):

// Server Component
function ProductPage({ product }) {
  return (
    <div>
      <h1>{product.name}</h1>
      
      {/* Server-rendered */}
      <p className="price">${product.price}</p>
      
      {/* Client-side interactivity */}
      <AddToCartButton productId={product.id} />
    </div>
  );
}

// Client Component
'use client';
function AddToCartButton({ productId }) {
  const handleClick = () => {
    // Add to cart logic
  };
  return <button onClick={handleClick}>Add to Cart</button>;
}

The pattern is the same. Server renders most of it, client handles interactivity.

So What Was The Point?

If we ended up back where we started, what was the point of the last 15 years?

Here’s what I think we actually gained:

1. Component Model

PHP’s include system was primitive:

<?php 
// You had to manually pass variables
$user_name = $user['name'];
$user_avatar = $user['avatar'];
include 'components/user-card.php'; 
?>

React components are self-contained:

<UserCard user={user} />

Props, composition, encapsulation—these are real improvements.

2. Type Safety

PHP was stringly-typed chaos:

// Is $user an array? An object? Who knows!
echo $user['nmae'];  // Typo? Runtime error. Maybe. Eventually.

TypeScript catches errors at build time:

function UserCard({ user }: { user: User }) {
  return <h1>{user.nmae}</h1>;  // ❌ Error: Property 'nmae' does not exist
}

3. Developer Experience

PHP debugging was var_dump() and prayer:

<?php
var_dump($user);
die();
// Refresh page, squint at output
?>

React has DevTools, hot reloading, and proper error boundaries:

// Change code, see result instantly
// Inspect component tree
// Time-travel through state changes

4. Ecosystem and Tooling

PHP had… PEAR packages and hope:

// Manual dependency management
// No tree shaking
// No code splitting
// Global scope pollution

Modern JavaScript has npm, bundlers, and sophisticated tooling:

npm install @tanstack/react-query
# Tree-shaken, typed, documented

5. The Choice of Where to Render

Old PHP: Everything server-rendered. Period.

Old SPAs: Everything client-rendered. Period.

Server Components: Choose per component.

// Server Component - no JS shipped
async function ProductDetails({ id }) {
  const product = await db.products.find(id);
  return <Details product={product} />;
}

// Client Component - interactive
'use client';
function ImageGallery({ images }) {
  const [selected, setSelected] = useState(0);
  return <Gallery images={images} selected={selected} onSelect={setSelected} />;
}

// Mix them
function ProductPage({ id }) {
  return (
    <>
      <ProductDetails id={id} />      {/* Server */}
      <ImageGallery images={images} /> {/* Client */}
    </>
  );
}

This granular control didn’t exist before.

What PHP Got Right

Looking back, PHP had some things figured out:

1. Simplicity

// This just worked
<?php echo "Hello, " . $_GET['name']; ?>

No build step. No configuration. No bundler. Write PHP, upload to server, it works.

We’ve lost some of that simplicity. A modern React project has:

  • Node.js
  • Package manager (npm/yarn/pnpm)
  • Bundler (webpack/vite/turbopack)
  • Transpiler (babel/swc)
  • Type checker (TypeScript)
  • Linter (ESLint)
  • Formatter (Prettier)

That’s a lot of complexity for “show data on a webpage.”

2. Progressive Enhancement

PHP pages worked without JavaScript:

<form action="/submit.php" method="POST">
  <input name="email" type="email" required>
  <button type="submit">Subscribe</button>
</form>

Form works. No JavaScript needed. Accessible. Fast.

SPAs broke this. Many modern web apps are blank without JavaScript.

Server Components can bring this back—forms that work without JS, content that’s visible immediately.

3. No Loading States for Initial Content

// User sees content immediately
<h1><?= $title ?></h1>
<p><?= $content ?></p>

vs.

// User sees spinner, then content
const [data, setData] = useState(null);
if (!data) return <Spinner />;
return <Content data={data} />;

Server Components eliminate loading states for server-rendered content. Just like PHP.

The Real Evolution

So here’s my revised mental model of web development history:

PHP (2005)
  "Server renders everything"

jQuery (2008)
  "Server renders, client enhances"

SPAs (2013)
  "Client renders everything"

SSR + Hydration (2016)
  "Server renders, client re-renders"

Server Components (2022)
  "Server renders most, client renders interactive parts"

We went from server → client → back to server, but we brought lessons with us:

  • From PHP: Server rendering is good for performance and SEO
  • From jQuery: Progressive enhancement matters
  • From SPAs: Component model, state management, developer experience
  • From SSR: Initial load performance matters

Server Components aren’t “going back to PHP.” They’re taking the best of everything we learned.

What I Tell Junior Developers

When someone new asks why we’re “going back to server rendering,” I explain:

  1. We’re not going backward. We’re taking a spiral path—revisiting old ideas with new understanding.

  2. The pendulum swings. Client-side went too far. Now it’s correcting. This is normal.

  3. Complexity has a cost. Sometimes the “old” way was simpler and that simplicity has value.

  4. Context matters. PHP was right for 2005. SPAs were right for 2015. Server Components are right for 2022. The web evolved.

  5. Learn the fundamentals. HTTP, HTML, CSS, how browsers work—these matter more than any framework. Frameworks come and go. The web stays.

The Beautiful Irony

The most ironic part? The PHP developers who never left server-side rendering?

They’re looking at us and laughing.

“Welcome back,” they say. “We’ve been here the whole time.”

And they’re not wrong.


P.S. — I showed this post to that senior developer. He said: “You forgot to mention that PHP 8 is actually pretty good now. It has types, attributes, and modern features. While you were all arguing about React vs Vue, PHP quietly got its act together.” He’s not wrong about that either.

SS

Saurav Sitaula

Software Architect • Nepal

Back to all posts
Saurav.dev

© 2026 Saurav Sitaula.AstroNeoBrutalism