DEV Community

Cover image for PS5 Exclusive Saros Delayed, But Pre-Orders Are Live Right Now
Bishoy Semsem
Bishoy Semsem

Posted on • Originally published at bishoy-bishai.github.io

PS5 Exclusive Saros Delayed, But Pre-Orders Are Live Right Now

The Next.js Performance Dream: "Saros" is Delayed, But the Foundation is Yours Today

Let's face it: in our world, speed isn't just a feature; it's a fundamental expectation. We've all been there, hammering away at a component, optimizing a useEffect hook, or meticulously pruning bundle sizes, all in the relentless pursuit of that "instantaneous load." The moment your app takes more than a blink to show meaningful content, you can practically hear the collective sigh of users bouncing off.

It's a universal problem, and one I've wrestled with on countless projects. The traditional approaches—client-side rendering (CSR) with hydration costs, or even static site generation (SSG) which struggles with highly dynamic content—often feel like trying to win a Formula 1 race with a street-legal car. They get the job done, but you're constantly pushing against inherent limitations.

This pursuit of ultimate performance, particularly for complex, data-rich UIs, is what led us, internally, to envision "Saros." Think of "Saros" as our codename for the holy grail of performant web development: a perfectly orchestrated, seamlessly streaming architecture built atop Next.js. It's about delivering critical content to the user's screen in milliseconds, then progressively streaming less critical elements, ensuring a butter-smooth, lightning-fast experience with minimal JavaScript on the client. It’s the kind of performance that feels like a "PS5 exclusive"—a premium, game-changing experience that you just know will redefine what's possible.

The Promise and the Paradox of Saros

The idea behind "Saros" is simple: leverage the full power of Next.js's App Router, specifically Server Components (RSCs) and Suspense, to radically shift rendering and data fetching to the server. Imagine:

  • Zero JavaScript for initial rendering of static and dynamic server components.
  • Colocated data fetching right within your components, simplifying code.
  • Streaming HTML and RSC payloads down to the client as soon as they're ready, instead of waiting for the entire page.

This isn't just about faster initial loads; it's about a fundamental paradigm shift that can virtually eliminate blank screens and provide a truly responsive feel, even on slow networks.

However, here's the thing about "Saros" – our ideal, perfected implementation for all scenarios is, well, a bit delayed. Like any truly ambitious "exclusive," getting it perfectly right for every edge case, every data dependency, every error state, and ensuring a smooth developer experience across a large team, is incredibly complex. It’s like designing a next-gen engine; the blueprint is genius, but the actual engineering to make it reliable and universally deployable takes time and iteration.

"Pre-Orders Are Live Right Now": Diving into Server Components and Suspense

The good news? You don't have to wait for the fully realized "Saros" dream. The core building blocks—the "pre-order" version, if you will—are incredibly powerful and available for you to use today in Next.js 13+ (and especially 14+). Let's look at how.

1. Embracing Server Components

In the App Router, every component is a Server Component by default, unless explicitly marked with "use client". This is a huge mental model shift. Instead of assuming client-side, you assume server-side.

Consider a component that fetches data:

// app/dashboard/layout.tsx (Server Component)
import { fetchUserProfile, fetchNotifications } from '@/lib/data';
import UserProfileCard from '@/components/UserProfileCard';
import NotificationsFeed from '@/components/NotificationsFeed';

export default async function DashboardLayout({ children }: { children: React.ReactNode }) {
  // Data fetching happens directly on the server, before any JS is sent to the client
  const user = await fetchUserProfile();
  const notifications = await fetchNotifications(user.id);

  return (
    <div className="flex h-screen bg-gray-900 text-gray-100">
      <aside className="w-64 p-4 border-r border-gray-700">
        <UserProfileCard user={user} />
        <NotificationsFeed notifications={notifications} />
      </aside>
      <main className="flex-1 p-8">
        {children}
      </main>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

In this example, UserProfileCard and NotificationsFeed can also be Server Components, further reducing client-side JS. Their data is fetched and rendered into HTML on the server.

Insight: I've found that the biggest shift is not how to fetch data, but where to fetch it. Think about data requirements at the component level, not just the page level. This co-location often leads to cleaner, more maintainable code.

2. Orchestrating with Suspense

This is where the magic of streaming truly comes alive, allowing you to show immediate UI while specific parts of the page are still loading. Next.js provides a built-in loading.tsx file convention, and you can use React's <Suspense> component for more granular control.

Let's say fetching notifications is slower than fetching the user profile. Without Suspense, the whole dashboard would wait. With Suspense:

// app/dashboard/layout.tsx
import { Suspense } from 'react';
import { fetchUserProfile, fetchNotifications } from '@/lib/data';
import UserProfileCard from '@/components/UserProfileCard';
import NotificationsFeed from '@/components/NotificationsFeed';
import Skeleton from '@/components/Skeleton'; // A simple loading skeleton

export default async function DashboardLayout({ children }: { children: React.ReactNode }) {
  const user = await fetchUserProfile(); // This might be fast

  return (
    <div className="flex h-screen bg-gray-900 text-gray-100">
      <aside className="w-64 p-4 border-r border-gray-700">
        <UserProfileCard user={user} />
        {/* Suspense Boundary: Renders fallback while NotificationsFeed loads */}
        <Suspense fallback={<Skeleton height="h-32" count={3} />}>
          <AsyncNotifications user={user} />
        </Suspense>
      </aside>
      <main className="flex-1 p-8">
        {children}
      </main>
    </div>
  );
}

// Separate async component to enable Suspense
async function AsyncNotifications({ user }: { user: any }) {
  const notifications = await fetchNotifications(user.id); // This might be slow
  return <NotificationsFeed notifications={notifications} />;
}
Enter fullscreen mode Exit fullscreen mode

Now, UserProfileCard renders immediately, and a Skeleton appears for NotificationsFeed until its data is ready, at which point the actual feed streams in. The user gets content faster, improving perceived performance.

In my experience: Granular Suspense boundaries are powerful, but don't overdo it. Too many small boundaries can make the UI feel jumpy. Aim for logical blocks of content that can load independently.

3. The use Hook (or Await Anywhere in RSCs)

For Server Components, you can await promises directly in your component body, eliminating the need for useEffect or client-side data fetching libraries. The use hook (available in React 18, and implicitly used when awaiting in RSCs) offers a similar capability for client components, allowing them to read promises from their parent Server Components.

// components/PostList.tsx (Server Component)
import { fetchPosts } from '@/lib/data';

export default async function PostList() {
  const posts = await fetchPosts(); // Await directly in RSC

  return (
    <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
      {posts.map((post: any) => (
        <article key={post.id} className="p-4 bg-gray-800 rounded">
          <h3 className="text-xl font-bold">{post.title}</h3>
          <p className="text-gray-400">{post.excerpt}</p>
        </article>
      ))}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

This pattern is incredibly clean and simplifies data flow significantly.

Pitfalls to Navigate on the Path to "Saros"

While the "pre-orders" are exciting, the path isn't without its challenges:

  1. The Client/Server Boundary Misconception: This is probably the biggest hurdle. You can't use browser APIs (like window or localStorage) in Server Components. Accidentally importing a "use client" component into a Server Component without understanding its implications can lead to hydration errors or unexpected behavior. Always be mindful of which environment your code is running in.
  2. Over-Suspensing vs. Under-Suspensing: As mentioned, too many small <Suspense> boundaries can lead to a "popcorn" effect where bits of UI appear in rapid succession, which can be disorienting. Conversely, too few means you're still waiting for large chunks of content, negating the benefits. Find the sweet spot for logical content blocks.
  3. Error Handling in Streaming: When a data fetch fails mid-stream, how do you recover gracefully? React's Error Boundaries become even more critical here. They allow you to catch errors in specific parts of your UI and render a fallback without crashing the entire page or stream.
  4. Debugging Complexity: The server-client boundary, combined with streaming, can make debugging trickier. Tracing where an error originated (server or client) requires a good understanding of the request-response lifecycle in Next.js.
  5. Cache Invalidation: Next.js introduces a robust caching mechanism for data fetching. Understanding when and how to revalidate server-side data (using revalidatePath, revalidateTag, or fetch options) is crucial for dynamic content.

Wrap-Up: The Journey to Hyper-Performance

"Saros," our internal ideal for a perfectly optimized, streaming Next.js application, might be a complex beast whose full potential is still being unlocked. The "delay" isn't a failure; it's a testament to the ambition and the inherent challenges of pushing the boundaries of web performance.

But let this not deter you. The "pre-orders" – Next.js Server Components, Suspense, and the App Router's data fetching capabilities – are a powerful toolkit available right now. They represent the most significant leap in web performance architecture in years.

Start experimenting. Build with these primitives. Embrace the new mental model. You'll not only deliver faster, more robust applications today, but you'll also be actively shaping the future of web development, ready for when the full "Saros" vision truly comes to fruition. Go ahead, make your users happy with speed.


> 🚀 Read on My Blog

Top comments (0)