
Next.js has firmly established itself as the go-to framework for production-grade React applications by combining:
- Hybrid Rendering: Seamlessly mix static and dynamic content to optimize for both SEO and interactivity.
- File-Based Routing: Intuitive, folder-driven routes that eliminate boilerplate and accelerate setup.
- Full-Stack Integration: Built-in API routes, edge functions, and data fetching keep back-end logic close to your UI.
With Next.js, Vercel introduces four major game-changers:
- Turbopack
- Server Actions
- Partial Prerendering (Experimental)
- App Router Advances & Edge Rendering
In this post, we’ll dive deep into each feature, showing you how to integrate them step-by-step, measure the impact on your metrics, and streamline your end-to-end workflow from code to global edge network.

Turbopack: Now Stable and Default
Turbopack is Vercel’s next-generation, Rust-based bundler for Next.js applications. Designed as the official successor to Webpack, it replaces multiple compiler passes with a single, unified pipeline that understands both server and browser targets in one go — enabling dramatic speedups in development builds and Hot Module Replacement (HMR).
Compared to Webpack, it delivers (Major Performance Wins):
- Faster cold starts: up to 50% quicker local development startup. Up to 76.7% faster local server startup on large apps like vercel.com, thanks to on-demand route compilation and optimized file I/O.
- Near-instant HMR: 90% faster hot module replacement cycles. Hot Module Replacement now consistently happens in under 10ms, regardless of application size.
- Up to 10x faster builds: Compilation times have been significantly reduced compared to Webpack. Delivers as much as 45.8% faster first-time route compilation by eliminating duplicate server/browser compiler passes and leveraging parallel module resolution.
- Memory efficiency: Uses up to 5x less memory than Webpack for large applications.
You can still opt out of Webpack through configuration to continue using it, but Turbopack’s performance benefits make it a compelling default choice.
Advanced Observability & Tracing
Turbopack instruments every step of its pipeline, writing lightweight trace files (without including your app code) in .next/trace-turbopack
. You can then visualize compile-time hotspots and memory usage with the built-in trace viewer, accelerating deep performance tuning and debugging.
Pro tip: you can get started as follows:
1. Enable Turbopack in your package.json scripts:
1
2
3
4
"scripts": {
"dev": "next dev --turbo",
"build": "next build"
}
2.Via CLI flag
1
next dev --turbo
3. (Optional) Enable in next.config.js
1
2
3
4
5
6
7
8
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
turbo: true
}
};
module.exports = nextConfig;
For Production is still in alpha Keep an eye on the Next.js release.
Who Benefits Most
Frontend Teams at Scale: Projects sharing code across packages — Turbopack’s incremental graph means changes in one package don’t force a full repo rebuild.
Full-Stack Developers: The unified server/client pipeline simplifies keeping API routes, edge functions, and UI in sync without juggling separate toolchains.
Enterprises & Agencies: Teams deploying on tight schedules or frequent release cycles; faster CI builds and dev iteration accelerate time-to-market.
Vercel & Edge-First Projects: When you want to leverage Vercel’s remote caching and edge functions, Turbopack’s cache persistence slashes both local and cloud build times.
Current Limitations & Roadmap
Webpack Compatibility
Turbopack doesn’t support raw webpack()configs, though most common loaders and resolve aliases work out of the box. Exotic Webpack child compilers or file-emitting plugins may not yet be supported.CSS & Sourcemaps
Switched from PostCSS to Lightning CSS for speed; uses section-based sourcemaps to avoid eval-based maps while still enabling granular mapping.Upcoming Optimizations
Export name mangling, scope hoisting, and production-optimized JS chunking are in progress — promising even smaller bundles and faster load times in future releases.
To explore more about Turbopack, you can read the following blog published by the Next.js team.

Server Actions
Server Actions, which were introduced as an experimental feature in previous versions, are now officially stable and production-ready inNext.js 15.0 onwards. This feature allows you to define server-side functions directly in your components, making form submissions and data mutations more straightforward and secure.
Gone are the days of separate API folders and custom endpoints. Server Actions let you define functions that run on the server, but invoke them directly from your React components:
Server Actions are simply async functions marked with the"use server" directive that always executes on the server, but can be invoked directly from your React components— whether Server or Client components—without you having to write any separate API route orfetch boilerplate.
Defining Server Action in a Client Component:
1
function addToCart(itemId: string, quantity: number) { … }
1
2
'use client'
import { addToCart } from './actions'
1
2
3
4
5
6
7
8
9
export default function ProductForm() {
return (
<form action={addToCart}>
<input name="itemId" value="123" hidden />
<input name="quantity" type="number" defaultValue={1} />
<button type="submit">Add to Cart</button>
</form>
)
}
We can also be called additionally from the function on the addItem action.
1
2
const addItem = addToCart.bind(null, '123')
// Now <form action={addItem}> only needs quantity in its form fields
We can define inline in a Server Component as shown below.
1
2
3
4
5
6
export default function Page() {
async function logout() {
'use server'
await auth.signOut()
}
}
1
2
return <button onClick={logout}>Log out</button>
}
To run Server-only: action:
1
2
3
4
5
export async function createUser(formData: FormData) {
// runs on the server only
const user = await db.user.create({ data: Object.fromEntries(formData) })
return user
}
Key Benefits of Server Actions
- Zero Boilerplate
- No API Folder: You don’t need to createpages/api or Route Handlers—just write your server logic next to your components.
- No fetch Calls: Instead of manually issuing, you invoke the action directly from your JSX (e.g., as a formaction or via an event handler).
- Collocated Full-Stack Logic
- Single File: UI and server logic live side by side. When you navigate to your component, you immediately see both the form and its mutation.
- Better Discoverability: Reduces context-switching between frontend and backend code — teams can onboard faster and understand features holistically.
- Automatic Type Safety & Serialization
- End-to-End Types (TypeScript): Your function’s signature is the single source of truth for both client and server, eliminating mismatched payload types.
- Optimized Rendering & Data Flow
- Fine-Grained Revalidation: Only the affected portions of the page are re-fetched or invalidated, avoiding full-page reloads or unnecessary data refetching.
- Streaming Updates: As soon as your server action returns data,Next.js can stream updated HTML to the client, improving perceived performance.
- Security by Default
- Server-Only Execution: Code under"use server" never ships to the browser, preventing accidental client exposure of sensitive logic or secrets.
- Automatic CSRF Protections: Form submissions via Server Actions include built-in safeguards against cross-site request forgery.
How It Differs from Traditional Development?
- Folder structure:
- Traditional API Routes: Separatepages/api orapp/api
- Server Action: No dedicated API folder
- Invocation:
- Traditional API Routes: Client issuesfetch() or Axios
- Server Action: Call function directly in JSX
- Boilerplate:
- Traditional API Routes: Request parsing, routing, and error handling are all manual
- Server Action: Next.js auto-generates the HTTP layer
- Data Fetching:
- Traditional API Routes: The Client mustfetch thensetState
- Server Action: Formaction or direct binding handles it
- Bundle Exposure:
- Traditional API Routes: All code shipped client-side unless tree-shaken
- Server Action:"use server" isolates server code completely
- Bundle Exposure:
- Traditional API Routes: Manual cache invalidation or SWR key updates
- Server Action: Next.js knows precisely which segments need refreshed
Limitations of Server Actions
- Only Serializable Data: Inputs and outputs must be JSON-friendly (no functions, class instances, file streams).
- No Low-Level HTTP Control: You can’t set custom headers, status codes, or handle multipart streams — use Route Handlers for that.
- Quick Mutations Only: Ideal for form submits or simple DB writes; avoid CPU-heavy tasks or long-running loops.
- App Router Required: Available only in the newapp/ directory — legacy pages/ projects must migrate first.
- Ecosystem Maturity: Some exotic middleware or plugin setups may not work yet; watch the Next.js changelog for fixes.
In short, Server Actions deliver a supremely smooth, zero-boilerplate way to co-locate your UI and server logic — but only when your data needs and runtime constraints fit within their streamlined model. When you need fine-grained HTTP control, non-serializable inputs, or heavy compute, classic API routes and edge functions still have your back. Mix and match both approaches, and you’ll get the best ofNext.js’s full-stack flexibility.

Partial Prerendering (PPR): Revolutionary Hybrid Rendering
Partial Prerendering (opt-in, experimental) streams a minimal HTML shell first, then progressively hydrates dynamic regions. Benefits include:
- Static shell is generated at build time for instant loading
- Dynamic content is streamed in after initial load
- Smart hydration prioritizes interactive elements, only hydrates interactive components on demand
- Improved Core Web Vitals: better scores for SEO rankings
- Use this for dashboards, blogs, or any route where SEO and interactivity matter in tandem.
Partial Prerendering (PPR) is an experimental rendering strategy introduced inNext.js 15 that lets you combine static and dynamic content within the same route. Instead of choosing “all static” or “all dynamic,” you can prerender the shell of your page at build time — layouts, navigation, static widgets — and defer only the truly dynamic pieces (user-specific data, real-time feeds) until request time.
Enable it in next.config.js:
You can enable PPR by adding the ppr option to yournext.config.ts file:
1
2
3
4
5
6
7
8
9
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
experimental: {
ppr: 'incremental',
},
}
export default nextConfig

Benefits of Partial Prerendering
- Instant Shell Load: Users see the page structure immediately.
- Personalized Data: Dynamic bits (user carts, recommendations) load seamlessly.
- Optimized Metrics: Improved TTFB and Largest Contentful Paint (LCP) by streaming only what’s needed.
Here’s an example showing how you might use Partial Prerendering to deliver SEO-critical content (meta tags, headings, core copy) at build time, while deferring non-essential or personalized sections to runtime:
1
2
3
4
5
// app/blog/[slug]/page.tsx
import { Metadata } from 'next';
import { Suspense } from 'react';
import fetchPost from '@/lib/fetchPost';
import fetchComments from '@/lib/fetchComments';
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
interface Params { params: { slug: string } }
// Generate static SEO metadata at build time
export async function generateMetadata({ params }: Params): Promise<Metadata> {
const post = await fetchPost(params.slug);
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.coverImage],
},
};
}
// Main page component
export default async function BlogPage({ params }: Params) {
const post = await fetchPost(params.slug);
return (
<>
{/* SEO tags rendered into the <head> at build time */}
<article className="prose lg:prose-xl mx-auto py-8">
<h1>{post.title}</h1>
<p className="text-gray-600">{post.publishedAt}</p>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
{/* Comments loaded at request time via PPR */}
<Suspense fallback={<p className="mx-auto text-center py-4">Loading comments…</p>}>
<Comments slug={params.slug} />
</Suspense>
</>
);
}
// Dynamic Comments component
async function Comments({ slug }: { slug: string }) {
const comments = await fetchComments(slug, { cache: 'no-store' });
return (
<section className="mx-auto max-w-2xl py-8">
<h2 className="text-2xl font-semibold mb-4">Reader Comments</h2>
<ul>
{comments.map(c => (
<li key={c.id} className="mb-2">
<strong>{c.author}</strong>: {c.text}
</li>
))}
</ul>
</section>
);
}
Why This Boosts SEO
- The generateMetadata function and the<article> markup are prerendered at build time, so crawlers immediately see your <title>,<meta> tags, headings, and main copy without waiting for any client-side JavaScript.
Deferred Non-Critical UI
Comments — useful for user engagement but not for SEO — are loaded after the shell streams, so they don’t block or bloat your initial HTML payload.Instant First Paint
Bots (and users) get the fully-formed article shell right away, improving Core Web Vitals like First Contentful Paint (FCP) and Largest Contentful Paint (LCP).
To understand it in detail, you can refer to and explore the following blog from Next.js.com
App Router Advances & Edge Rendering
The App Router gets smarter in v14. The App Router has been enhanced with:
1. Route Groups
Route Groups let you organize your folder structure for clarity without affecting the URL path. This is ideal for splitting codebases by feature or team, while keeping public-facing routes clean.
1
2
3
4
5
6
app/
(marketing)/ ← “marketing” is a group, not part of the URL
page.js → served at “/”
about/page.js → served at “/about”
(dashboard)/ ← admin code separated into its own group
page.js → served at “/dashboard”
- Encapsulate shared layouts, styles, and components per group.
- No URL bloat: folders in parentheses are stripped out of the route.
- Improves DX for large apps by grouping related code.
2. Parallel Routes:
Parallel Routes enable rendering multiple “slots” in the same URL segment, such as a sidebar, map, or notifications panel alongside your main content.
1
2
3
4
5
6
7
8
app/
└── dashboard/
├── layout.js ← your shared layout
├── page.js ← the default “children” slot
├── @analytics/ ← “analytics” slot
│ └── page.js ← rendered into the `analytics` prop
└── @team/ ← “team” slot
└── page.js ← rendered into the `team` prop
1
2
3
4
5
// app/dashboard/layout.js
export default function DashboardLayout({ children, analytics, team }) {
return (
<div className="dashboard">
<section className="main">{children}</section>
1
2
3
4
5
6
7
8
9
10
11
<aside className="analytics">
<h2>Analytics</h2>
{analytics}
</aside>
<aside className="team">
<h2>Team</h2>
{team}
</aside>
</div>
)
}
- children ← rendersapp/dashboard/page.js
- analytics ← renders whatever you export fromapp/dashboard/@analytics/page.js
- team ← renders whatever you export fromapp/dashboard/@team/page.jsNext.js
Each slot folder just needs a page.js. For example:
1
2
3
4
// app/dashboard/@analytics/page.js
export default function Analytics() {
return <div>📈 Site traffic, charts, key metrics…</div>;
}
1
2
3
4
// app/dashboard/@team/page.js
export default function Team() {
return <div>👥 List of team members, roles, avatars…</div>;
}
you can navigate between parallel pages as:
1
2
// anywhere in your dashboard UI
import Link from 'next/link';
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// anywhere in your dashboard UI
import Link from 'next/link';
export default function DashboardNav() {
return (
<nav>
{/* Changes only the analytics slot */}
<Link href="/dashboard/@analytics/views">View Analytics</Link>
{/* Changes only the team slot */}
<Link href="/dashboard/@team/list">Team Directory</Link>
</nav>
);
}
// Use <Link> or router.push() as usual—Next.js will swap out only the
// slot that corresponds to the new route, leaving other slots intact
Example URLs
- /dashboard
- Renders default children, plus both @analytics and @team default pages
- /dashboard/@analytics/views
- Swaps the analytics slot to app/dashboard/@analytics/views/page.js (if it exists) and keeps the team slot unchanged
- /dashboard/@team/list
- Swaps the team slot to app/dashboard/@team/list/page.js and preserves analytics
For more information, you can click on Parallel Routes in Next.js.
Benefit
- Build complex UIs (e.g. dashboards, admin panels) without manual composition.
- Each slot can fetch its own data and show independent loading states.
- Child navigation only re-renders affected slots, preserving others.
Notes:
- In the app directory, create folders prefixed with
@
for each slot you want to render in parallel. - These folders don’t affect the URL path—they simply declare “named slots” that your layout can accept as props.
3. Streaming & Loading UI
Built on React 18’s streaming Suspense, Next.js lets you ship partial HTML as soon as each segment is ready — rather than waiting for the entire tree.
1
2
3
4
app/
products/
loading.js ← shows while product list is fetching
page.js ← streams in as soon as data a
1
2
3
4
5
6
7
'use server'
async function fetchProducts() { … } // Use hook to get data
export default async function ProductsPage() {
const products = await fetchProducts()
return <ProductList products={products} />
}
In client components you can:
1
const products = use(fetchProducts())
- Instant “shell” UI so users see structure immediately.
- Granular loading indicators vs. a single spinner.
- Better Core Web Vitals (especially FCP & LCP) by streaming critical HTML.
4. Simplified Error and Redirect Handling
Error Boundaries: Drop an error.js file alongside any page.js or layout.js to scope error handling:
1
2
3
4
5
6
7
8
9
app/
dashboard/
error.js ← catches errors in dashboard subtree
page.js
// app/dashboard/error.js
export default function DashboardError({ error }) {
return <div>Oops! {error.message}</div>
}
Redirect Helpers: Programmatic redirects from Server Components — no more res.writeHead or getServerSideProps:
Benefits
- Keep your server-only logic collocated.
- TypeScript-friendly APIs
- More predictable routing behavior with fewer edge cases.
5. Nested Layouts: Define shared UI at any level; child pages inherit and preserve state across navigations.
1
2
3
4
5
6
app/
layout.js ← root layout (header, footer)
dashboard/
layout.js ← wraps dashboard pages (sidebar)
page.js
about/page.js
We can have different layouts based on route groups. In the above example:
- app/layout.js can be applied at the global level,
- dashboard/layout.js can apply for pages related to dashboard.
6. Template Routes
In Next.js 15, a template.js file behaves similarly to a layout—it wraps child pages or layouts—but with one crucial difference: it remounts on every navigation, giving you a fresh component instance (and state) each time.
This makes templates ideal for per-page side-effects and UI that intentionally resets between routes.
Create template.js alongside your page.js or layout.js
Place template.js in any route folder:
1
2
3
4
5
6
app/
└── blog/
├── layout.js ← shared shell (persists across pages)
├── template.js ← remounts on each blog page navigation
└── [slug]/
└── page.js ← your individual
Export a React component accepting children
1
2
3
4
5
6
7
8
9
10
// app/blog/template.js
export default function BlogTemplate({ children }) {
// runs on every navigation to a new blog page
console.log('Template mounted')
return (
<div className="blog-template">
{children}
</div>
)
}
When to Use template.js
- Per-Page Analytics or Logging: Run a page-view tracking call on every navigation, without worrying about duplicate or skipped triggers.
- Transient UI: Display UI that must reset between pages (e.g., feedback forms, per-page modals).
- Custom Suspense Behavior: Unlike layouts — where Suspense fallbacks only show once per layout mount — templates re-show their fallback on each navigation when used withloading.js.
template.js vs. layout.js
- layout.js: Persists across child navigations whiletemplate.js: Remounts on each navigation
- layout.js, State &useEffect Preserved whiletemplate.js: state anduseEffect reset and refires
Next.js is packed with even more quality-of-life updates that we’ll dive into in upcoming posts, including:
- Enhanced <Image> component capabilities (AVIF support, automatic sizes inference, smarter placeholders)
- New Server Component patterns for cleaner data fetching and UI co-location
- Metadata API enhancements that simplify SEO tags, Open Graph data, and dynamic<head> management
- Developer experience improvements like refined error overlays, faster type-checking, and improved telemetry
You can also explore more detail on https://nextjs.org/blog/next-14.
Conclusion
Next.js represents a significant step forward in the evolution of React frameworks. With stable Turbopack, mature Server Actions, revolutionary Partial Prerendering, and numerous other improvements, this release sets a new standard for web application development.
Whether you’re building a small personal project or an enterprise-grade application,Next.js provides the tools and optimizations needed to deliver exceptional user experiences with developer-friendly workflows.
Contact Information
Say something to start a live chat!