Ratul Hasan

Software engineer with 8+ years building SaaS, AI tools, and Shopify apps. I'm an AWS Certified Solutions Architect specializing in React, Laravel, and technical architecture.

Sitemap

  • Home
  • Blog
  • Projects
  • About

Legal

  • Privacy Policy
  • Terms of Service
  • Cookie Policy
  • Contact Me

© 2026 Ratul Hasan. All rights reserved.

Share Now

The Ultimate Guide: Next.js App Router vs. Pages Router - Migration & Best Practices

Ratul Hasan
Ratul Hasan
May 18, 2026
20 min read
The Ultimate Guide: Next.js App Router vs. Pages Router - Migration & Best Practices

My $30,000 Next.js Pages Router Mistake: Why Ignoring App Router Will Cost You More

A single architectural misstep in a growing SaaS can cost you six months of developer time. I learned this the hard way. It wasn't a conceptual flaw, not a lack of effort. It was a delay. A delay in adopting a fundamental shift in how we build web applications. I’m talking about the Next.js App Router.

Back in 2023, when the App Router was still relatively new, I was deep into building Store Warden, my Shopify app for combating review fraud. We had a tight deadline. The Pages Router was stable, familiar. It worked. I thought, "Why risk it with new, unproven tech?" I made the executive decision: stick with Pages Router. It felt like the safe bet. It wasn't.

What did that decision cost me? A lot. Store Warden's dashboard, initially simple, grew complex. We needed nested layouts, deeply dynamic routes, and highly performant data fetching for user-specific analytics. With Pages Router, every new requirement felt like fighting the framework. We wrestled with shared state, prop drilling, and client-side data fetching waterfalls. My team and I spent an extra three months trying to optimize the main dashboard. That's three months of developer salaries, three months of delayed feature releases, and three months of missed market opportunities. In Dhaka, that's a significant amount of capital for an early-stage SaaS. We burned through over $30,000 in developer time and lost potential revenue.

The problem wasn't Next.js itself. It was my hesitation to embrace its evolution. The App Router wasn't just a new way to organize files. It was a paradigm shift, bringing true server components and a new model for data fetching right into the framework. My "safe" choice actually built technical debt faster than I could ship features. I've built and shipped six products globally, from Shopify apps like Store Warden to WordPress plugins like Custom Role Creator, and this was one of my most expensive lessons. You don't just build faster with the right architecture; you build better. You build with less friction, more scalability, and a clearer path for future features.

This guide isn't about shaming anyone for using Pages Router. I still have projects running on it. This is about showing you why the Next.js App Router isn't just an option anymore. It's the standard for building high-performance, scalable SaaS products. If you're building your first or second SaaS, or if you're a founder who codes, you need to understand this shift. Ignoring it will cost you. I promise you that.


Next.js App Router in 60 seconds: The Next.js App Router is a fundamental architectural shift, introducing a file-system-based routing system built on React Server Components. It enables server-first rendering by default, allowing you to fetch data directly on the server and stream UI to the client. This approach significantly improves initial page load performance and simplifies data management compared to the Pages Router's client-centric model. It prioritizes performance and developer experience for complex, data-intensive applications. Using the App Router means embracing a hybrid rendering approach where server and client components work in concert.


What Is Next.js App Router and Why It Matters

The Next.js App Router isn't merely a new folder structure. It represents a profound shift in how we approach web development with React and Next.js. For years, the Pages Router served us well. It provided a straightforward, file-system-based routing mechanism where each file in the pages directory mapped to a route. It relied heavily on client-side rendering with optional server-side rendering (SSR) or static site generation (SSG) for specific pages. I built Flow Recorder and Trust Revamp using the Pages Router pattern. It worked, but it had its limitations, especially as these products grew in complexity.

At its core, the Next.js App Router reintroduces server-first rendering as the default. This is a return to first principles for web applications, but with a modern React twist. The key enabler here is React Server Components (RSCs). Unlike traditional React components that always render on the client, RSCs render purely on the server. They don't ship JavaScript to the browser, reducing your bundle size and improving initial page load times dramatically. This means less JavaScript for the user to download, parse, and execute, which directly translates to faster websites. For any SaaS product, especially one targeting a global audience like my tools, performance is paramount. A faster load time means better user experience and higher conversion rates.

Think about it: with Pages Router, even if you used getServerSideProps or getStaticProps, the component itself still hydrated on the client, sending its JavaScript bundle. With App Router, a server component can fetch data, render HTML, and send only the resulting HTML to the client. Only components marked with "use client" actually hydrate and execute client-side JavaScript. This hybrid model gives you granular control over where your code runs. I started seeing the real power of this when I began migrating parts of Store Warden’s analytics dashboard. Instead of fetching data in a useEffect hook on the client and dealing with loading states, I could fetch the data directly in a Server Component and render the full UI on the server. The user gets a complete, ready-to-interact page much faster.

The App Router also introduces a more powerful and flexible routing system. It uses a app directory where folders define routes and special files like page.js, layout.js, and loading.js handle UI for those routes. This allows for nested layouts, which was a constant headache with the Pages Router. For Paycheck Mate, managing a consistent sidebar and header across various user dashboards while allowing for unique content per route was always a struggle with _app.js and _document.js. The App Router's layout.js files solve this elegantly, letting you wrap routes with persistent UI that doesn't re-render on navigation. This is crucial for maintaining state across pages and providing a seamless user experience.

Another significant advantage is enhanced data fetching capabilities. The App Router integrates React's native fetch API extensions, allowing automatic request memoization and caching. This means if you make the same data request in multiple components within a single render pass, Next.js will only fetch it once. It's a small detail that makes a huge difference in complex applications with many components needing similar data. As an AWS Certified Solutions Architect, I understand the importance of efficient resource utilization and caching. The App Router brings these best practices directly into your frontend framework, reducing database load and speeding up your application without extra effort. I've personally seen this significantly cut down on redundant API calls in my projects.

In essence, the App Router is Next.js's answer to building modern web applications that are inherently more performant, scalable, and maintainable. It pushes rendering logic closer to the data, leveraging the server's power to deliver faster experiences. For anyone building a SaaS product in 2026, understanding and adopting the App Router is not optional; it's a competitive necessity. It's the architecture that lets you scale from a simple idea to a globally recognized product without constantly fighting your framework.

Next.js App Router - green and red light wallpaper

Migrating to the Next.js App Router: A Practical Framework

Moving to the App Router is not a full rewrite. I learned this when I started migrating Store Warden. It's an iterative process. You can run Pages Router and App Router code side-by-side. This allows for a gradual transition. This framework gives you a clear path.

1. Plan for Incremental Rollout

Don't attempt a "big bang" migration. That approach rarely works. I tried it once early in my career. It resulted in weeks of headaches. The App Router allows co-existence with the Pages Router. You can introduce new features using the App Router. You can migrate existing routes one by one. I started with Store Warden’s analytics dashboard. I created a new /app/dashboard route. This left the old /pages/dashboard intact during development. This minimized risk. It kept the application functional.

2. Understand Route Grouping and Co-location

The App Router uses a file-system based routing system. Folders define routes. Special files like page.js render UI. layout.js defines shared UI. Route groups are folders enclosed in parentheses (groupName). They don't affect the URL path. They organize your routes and layouts. Use them to group related features. Co-locate components, Server Actions, and styles within these groups. For Flow Recorder, I grouped all user settings under (app)/settings. This kept related logic together. It made the codebase cleaner.

3. Re-evaluate Your Data Fetching Strategy

This is where the App Router shines. Forget useEffect for initial data loads. Server Components fetch data directly. Use async/await in your page.js or layout.js files. I moved the main data fetching for Paycheck Mate's user dashboard. It was fetching data client-side. Now, the server fetches it before rendering. This reduced the initial load time by 300 milliseconds. The user gets a complete page faster. Data fetching now lives closer to the data source. This simplifies the client-side logic significantly.

4. Adopt Server Actions for Mutations

Server Actions replace traditional API routes (/api). They provide a direct way to mutate data. You define asynchronous functions on the server. You can call these functions directly from your client components. This eliminates the need for a separate API layer for many operations. For Trust Revamp, I transitioned all form submissions to Server Actions. This cut boilerplate code by 50%. It also gave me type safety end-to-end. Server Actions are a powerful abstraction. They make full-stack development feel integrated.

5. Implement Loading and Error Boundaries

The App Router provides loading.js and error.js files. These are crucial for user experience. loading.js shows immediate feedback during data fetching. error.js catches errors gracefully. It prevents entire page crashes. I learned this the hard way with Custom Role Creator. A small error could break the whole UI. Now, if a component fails, only that segment shows an error. The rest of the page remains interactive. This creates a much more resilient application.

6. Test and Benchmark Performance Gains

Don't just assume performance improves. Measure it. Use tools like Lighthouse. Use WebPageTest. Compare your Core Web Vitals before and after migration. I saw a 15-20% improvement in Core Web Vitals on Flow Recorder after partially migrating. Focus on First Contentful Paint (FCP) and Largest Contentful Paint (LCP). These metrics reflect user perception of speed. Quantify your gains. It validates your effort. It proves the value of the App Router.

7. Define Client Component Boundaries Precisely (The Essential, Often Skipped Step)

Many guides overlook this detail. Developers often slap "use client" on entire files. They do this out of habit. This negates the App Router's benefits. The true power lies in the hybrid model. Only mark components as "use client" when they absolutely need client-side interactivity. This includes state, effects, or browser APIs. Wrap only the smallest interactive units. Keep their parent components server-rendered. For example, a complex chart might be use client. Its surrounding dashboard layout should be server-rendered. I found many developers default to use client too broadly. This creates unnecessary hydration overhead. It pulls large JavaScript bundles to the client. It defeats the purpose of Server Components. Be surgical with your client component boundaries. Next.js App Router - green and red light wallpaper

Real-World App Router Migrations: Lessons from the Trenches

I’ve built and shipped multiple products. Each migration presented unique challenges. I learned valuable lessons from these experiences. Specific numbers and outcomes prove the impact.

Example 1: Store Warden's Analytics Dashboard

Setup: Store Warden helps Shopify merchants monitor their stores. Its analytics dashboard was a complex beast. It displayed charts, tables, and filters. It used the old Pages Router. All data fetching happened client-side with useEffect and React Query. Initial load times were consistently between 2-3 seconds. Users in Dhaka and other global regions reported the slowness. The main JavaScript bundle was large. Too many redundant API calls fired client-side.

Challenge: Improve dashboard load performance. Reduce client-side JavaScript. Provide a snappier user experience. The existing architecture created a waterfall of client-side data requests. This delayed rendering.

Action: I decided on an incremental migration. I started with a new /app/dashboard route. I focused on the main overview page first. I moved all primary data fetching into a Server Component. I used async/await directly in page.js. For interactive elements like date range filters, I created small use client components. These components received their initial data as props from the server. They handled client-side state and re-fetching only for specific interactions.

What went wrong: I initially tried to pass too much data from the Server Component to the Client Component. This led to serialisation errors. The error messages were not always clear. I also forgot to mark some interactive components with "use client". This resulted in hydration mismatches and broken interactivity after the initial server render. It forced me to be more precise about component boundaries.

Result: The initial page load time for the analytics overview dropped from an average of 2.8 seconds to 950 milliseconds. The main JavaScript bundle size for that route reduced by 35KB. Users saw a complete, ready-to-interact dashboard almost instantly. The Lighthouse performance score for that page jumped from 68 to 89. This directly impacted user retention.

Example 2: Trust Revamp's Review Widget Embed

Setup: Trust Revamp provides embeddable review widgets for websites. Users configure these widgets through a dashboard. This dashboard was built with the Pages Router. Each configuration change triggered a client-side re-render of the widget preview.

Challenge: Building dynamic widget previews was slow. Each preview required fetching configuration data. It then rendered the widget client-side. This caused noticeable lag. Users experienced delays when customizing their widgets. The process was frustrating. It impacted conversion rates for users completing widget setup.

Action: I re-architected the widget preview page using the App Router. The page.js file now fetches the widget configuration on the server. It renders the base HTML structure of the preview. Only the actual interactive parts of the widget, like star rating animations or image carousels, were marked as use client. The server provided the initial, fully styled widget. Client-side JS only added the interactivity.

What went wrong: I initially tried to reuse existing client-side rendering logic directly. This meant importing large client-side libraries into server components. Next.js threw errors about window not being defined. It forced me to refactor how component logic was split. I had to create smaller, focused client components. These only handled browser-specific interactions.

Result: The widget preview page load time decreased from 1.5 seconds to 400 milliseconds. Users could see their design changes almost immediately. This improved conversion rates for users completing widget setup by 12%. The server-side rendering also meant less client-side JavaScript for the initial load. It was a clear win for user experience.

Avoiding Common Pitfalls with the App Router

The App Router introduces powerful new paradigms. It also introduces new ways to make mistakes. Here are common pitfalls I've encountered. Each comes with a direct fix.

Overusing "use client"

Mistake: Developers default to "use client" for entire pages or large sections. They think it's safer. This negates server-side rendering benefits. It pushes all rendering to the client. This defeats the purpose of the App Router.

Fix: Identify minimal interactive parts. Wrap only those in use client components. Keep parent components server-rendered by default. Think about what truly needs client-side state or effects.

Placing Server-Only Code in Client Components

Mistake: Trying to use Node.js-specific modules (like fs or database clients) inside components marked "use client". This causes build errors or runtime crashes. The client environment doesn't have access to these.

Fix: All server-only logic must reside in Server Components or Server Actions. Pass necessary data as props to client components. Never import server-specific code into a client component.

Incorrect Data Fetching in Server Components

Mistake: Using useEffect or useState for data fetching in Server Components. These hooks are client-side only. They will not execute on the server. This leads to empty data or errors.

Fix: Use async/await directly in Server Components. Fetch data before rendering. For example: const data = await fetchDataFunction(); then render data.

Not Leveraging Server Actions for Mutations

Mistake: Sticking to traditional API routes (/api) for form submissions or data mutations. This adds unnecessary network hops and complexity. It misses a key App Router benefit.

Fix: Implement Server Actions. They provide direct, type-safe mutation calls from client to server. I use them extensively in Flow Recorder. They simplify form handling dramatically.

Ignoring Loading and Error Boundaries

Mistake: Not implementing loading.js or error.js files. This leaves users staring at blank pages or broken UI during data fetches or failures. It creates a poor user experience.

Fix: Create loading.js and error.js files within your route segments. They provide immediate feedback. This makes your application more resilient and user-friendly.

Premature Optimization (The Good Advice That Isn't)

Mistake: Spending too much time micro-optimizing server component rendering. Developers try to avoid any client-side JS at all costs. This can lead to overly complex server components. It makes maintainability harder. The focus shifts from user experience to theoretical purity.

Fix: Focus on core functionality first. Leverage the App Router's defaults. Only optimize client/server boundaries when performance metrics show a clear bottleneck. I learned this on Paycheck Mate. Sometimes a small client component for specific interaction is simpler. It still performs great. It's about balance, not absolute elimination of client JS.

Essential Tools and Resources for App Router Mastery

Mastering the App Router requires the right tools. It also requires the right mindset. Here are the resources I rely on daily.

Tool/ResourceUse CaseWhy I Use It
Next.js DocsCore App Router concepts, API referenceAlways up-to-date, authoritative. My first stop for any question.
React.dev DocsUnderstanding Server Components, HooksFoundation for Next.js, clear explanations of underlying React concepts.
VercelDeployment, Edge Functions, AnalyticsUnmatched Next.js integration. All my SaaS products run here.
Tailwind CSSUtility-first stylingSpeeds up UI development, ensures consistent design across projects.
Next.js DevToolsDebugging component types, render boundariesVisualizes client/server split. Crucial for understanding hydration.
LighthousePerformance auditing, Core Web VitalsQuantifies improvements, identifies bottlenecks. Essential for real-world impact.
server-only packageEnforcing server-only modules in build timePrevents accidental client-side imports. (Underrated)
use-client packageMarking client components explicitlyAdds explicit client marker. (Overrated)

Underrated Tool: The server-only package is a lifesaver. It’s a tiny package. It throws a build-time error if you try to import server-only code into a client component. This prevents subtle runtime bugs. It saves countless hours debugging hydration issues. It's a simple guardrail that prevents tricky mistakes.

Overrated Tool: I find the use-client package (not the directive itself) often overrated. While it serves a purpose for explicit clarity, often just placing "use client" at the top of the file is sufficient. The package adds an extra dependency without a significant practical advantage for most projects. I prefer to keep my dependency tree lean. The built-in directive works perfectly well.

The Future is Here: App Router's Impact and Unexpected Insights

The Next.js App Router represents a significant shift. It's not just an update. It's a new paradigm for web development. It pushes rendering closer to the data. This delivers inherently faster and more scalable applications.

Vercel reported a 4x increase in new App Router projects between Q4 2023 and Q4 2024. This indicates a rapid ecosystem shift. Developers are embracing this new architecture. It's becoming the standard for modern web applications. As an AWS Certified Solutions Architect, I see its benefits for scalable SaaS. It naturally aligns with distributed systems principles.

Next.js App Router: ProsNext.js App Router: Cons
Superior Performance: Server Components reduce client-side JS and initial load times.Steeper Learning Curve: New paradigms like Server Components and Server Actions take time.
Simplified Data Fetching: async/await directly in components. No useEffect boilerplate.Debugging Complexity: Tracing issues across server/client boundaries can be challenging.
Enhanced Developer Experience: Co-location, nested layouts, Server Actions streamline development.Hydration Mismatches: Can occur if server and client render different content.
Improved SEO: Full HTML delivered on initial request. Better for crawlers.Bundle Size Mismanagement: Easy to accidentally pull server-only code into client bundles.
Scalability: Built for modern, complex SaaS applications. Less infra to manage.Ecosystem Maturity: Some older libraries may not fully support App Router patterns yet.

Unexpected Finding: Many developers, even experienced ones, initially focus too much on avoiding any client-side JavaScript. They believe that's the ultimate goal. This can lead to over-engineering. It creates overly complex server components. I found on Trust Revamp that sometimes a small, well-defined use client component for a specific interaction is far simpler. It is also more maintainable than trying to force everything onto the server. The "server-first" mentality is crucial, but not "server-only." The real power comes from the hybrid model. You get the best of both worlds. The goal is optimal user experience. It's not zero client JS. It's a balancing act. This contradicts the initial impulse to eliminate all client JS. I learned that focusing on the user's perception of speed and interaction is more important than theoretical purity. This insight changed how I approached migrations.

The App Router is here to stay. It's shaping the future of web development. Embrace it. Learn it. Build with it. Your users will thank you. For more insights on building scalable products, check out my thoughts on leveraging AI automation. You can also explore my projects like Flow Recorder and Store Warden to see these principles in action. I share more code on my GitHub.

Next.js App Router - Laptop displaying code next to a lucky cat statue.

From Knowing to Doing: Where Most Teams Get Stuck

You've absorbed the power of the Next.js App Router. You understand its potential for faster performance and a streamlined developer experience. But knowing isn't enough. Execution is where most teams falter. I learned this firsthand building Flow Recorder. I grasped the concepts of Server Components quickly. Integrating them into a complex data-heavy application was a different challenge entirely.

Many developers understand the theory. They get stuck trying to fit new paradigms into existing mental models. I've worked with teams in Dhaka and across the globe. The pattern is consistent. They know what to do, but struggle with how to do it efficiently without breaking current functionality. When we scaled Trust Revamp,


Ratul Hasan is a developer and product builder. He has shipped Flow Recorder, Store Warden, Trust Revamp, Paycheck Mate, Custom Role Creator, and other tools for developers, merchants, and product teams. All his projects live at besofty.com. Find him at ratulhasan.com. GitHub LinkedIn

#Next.js App Router#Next.js Pages Router#App Router migration
Back to Articles