Building Modern Web Apps with Next.js 15
Why Next.js 15 Changes Everything
The web development landscape has shifted dramatically. With Next.js 15, the App Router and React Server Components aren't just incremental improvements — they represent a fundamental rethinking of how we build web applications.
At Rosecraft Studios, we've migrated production applications to Next.js 15 and seen measurable improvements across the board: smaller JavaScript bundles, faster Time to First Byte, and a developer experience that finally makes server-side rendering feel natural.
React Server Components: The Core Shift
React Server Components (RSC) allow components to render entirely on the server, never shipping their JavaScript to the client. This isn't SSR as we knew it — it's a new rendering paradigm.
// This component runs on the server. Zero client-side JS.
export async function RecentProjects() {
const projects = await db.project.findMany({
where: { published: true },
orderBy: { createdAt: 'desc' },
take: 6,
});
return (
<section>
{projects.map((project) => (
<ProjectCard key={project.id} project={project} />
))}
</section>
);
}
The key insight: data fetching lives where the data is consumed. No more prop drilling from getServerSideProps, no more global state management for server data.
The App Router Architecture
The App Router introduces a file-system based routing model with powerful conventions:
Layouts and Templates
Layouts persist across navigations, preserving state and avoiding unnecessary re-renders. Templates re-mount on every navigation, useful for entrance animations.
src/app/
├── layout.tsx # Root layout (shared header, footer)
├── template.tsx # Page transition wrapper
├── page.tsx # Homepage
├── blog/
│ ├── layout.tsx # Blog-specific layout (sidebar TOC)
│ ├── page.tsx # Blog index
│ └── [slug]/
│ └── page.tsx # Individual blog posts
Static Generation with Dynamic Data
generateStaticParams pre-renders pages at build time while generateMetadata provides per-page SEO:
export async function generateStaticParams() {
const posts = getAllPosts();
return posts.map((post) => ({ slug: post.slug }));
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params;
const post = getPostBySlug(slug);
if (!post) return {};
return {
title: post.frontmatter.title,
description: post.frontmatter.description,
};
}
Performance Wins We've Measured
After migrating a client's SaaS dashboard from Pages Router to App Router:
| Metric | Before | After | Improvement |
|---|---|---|---|
| First Load JS | 287 KB | 142 KB | 50% reduction |
| Time to First Byte | 1.2s | 0.3s | 75% faster |
| Lighthouse Performance | 72 | 96 | +24 points |
| Largest Contentful Paint | 3.1s | 1.4s | 55% faster |
The biggest win came from moving data fetching to Server Components. Previously, the client loaded a 45 KB state management library just to cache API responses. With RSC, that entire layer disappears.
Streaming and Suspense
Next.js 15 leverages React Suspense for streaming HTML to the browser. Heavy components don't block the entire page:
import { Suspense } from 'react';
export default function DashboardPage() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<MetricsSkeleton />}>
<AnalyticsMetrics />
</Suspense>
<Suspense fallback={<ActivitySkeleton />}>
<RecentActivity />
</Suspense>
</div>
);
}
The shell renders instantly. Each Suspense boundary streams its content as it resolves. Users see a meaningful page in milliseconds, with data filling in progressively.
Server Actions: Mutations Without API Routes
Server Actions bring form handling back to its simplest form — a function that runs on the server:
'use server';
export async function submitContactForm(formData: FormData) {
const name = formData.get('name') as string;
const email = formData.get('email') as string;
const message = formData.get('message') as string;
await db.contactSubmission.create({
data: { name, email, message },
});
await sendNotificationEmail({ name, email, message });
revalidatePath('/admin/submissions');
}
What We Recommend
For new projects, Next.js 15 with the App Router is our default recommendation. The combination of Server Components, streaming, and Server Actions provides a foundation that scales from marketing sites to complex SaaS applications.
The learning curve is real, but the payoff — smaller bundles, faster pages, simpler data flow — makes it worthwhile for any team serious about web performance.
Ready to modernize your web application? Get in touch to discuss how Next.js 15 can improve your product's performance and user experience.
