Stop Using Next.js Like It's 2022

Prashant Adhikari
Apr 23, 20254 min read
Next.js has evolved. Have you?
In 2022, Next.js was all about the Pages Router, client-side hydration by default, and manual API routes. You probably scaffolded your app with create-next-app, threw together a few pages in the pages directory, and called it a day. You used getServerSideProps for everything, ignored static optimization, and wondered why your TTFB (Time to First Byte) was slower than a dial-up connection.
Fast-forward to 2024: That approach is costing you performance, scalability, and developer joy. Next.js today is a streamlined, server-first framework with built-in optimizations—if you use it right.
Let’s break down the outdated habits that need to die and how to embrace the modern Next.js workflow.
❗️ The Old Habits That Need to Die
1. Clinging to the Pages Router
The Pages Router isn’t deprecated (yet), but the App Router is where the future lives. Still organizing your app in /pages? You’re missing out on:
Layouts that persist across routes
React Server Components (RSC) by default
Streaming, Suspense, and built-in loading states
Old (2022):
// pages/about.js
export default function About() {
return <div>About</div>;
}New (2024):
// app/about/page.js
export default function Page() {
return <div>About</div>;
}Bonus: Add a loading.js file for automatic Suspense fallbacks.
2. Ignoring React Server Components
Stop shipping unnecessary JavaScript to the client. If your component doesn’t need interactivity, it shouldn’t have useState or useEffect—it should be a Server Component.
Old: Client-side rendered component bloating your bundle:
// components/News.js (client)
'use client';
import { useState, useEffect } from 'react';
export default function News() {
const [data, setData] = useState([]);
useEffect(() => { /* fetch data */ }, []);
return /* render data */;
}New: Server Component fetching data at the source:
// app/news/page.js
async function fetchData() { /* ... */ }
export default async function Page() {
const data = await fetchData();
return /* render data */;
}3. Overusing SSR When Static Is Better
Not everything needs server-side rendering. Static site generation (SSG) and Incremental Static Regeneration (ISR) are still your friends for performance.
Old: Using getServerSideProps For a blog:
export async function getServerSideProps() {
const posts = await fetchPosts();
return { props: { posts } };
}New: Generate static pages with on-demand revalidation:
// app/blog/[slug]/page.js
export async function generateStaticParams() {
const posts = await fetchPosts();
return posts.map(post => ({ slug: post.slug }));
}
export async function GET({ params }) {
const post = await fetchPost(params.slug);
return /* render post */;
}4. Manual API Routes for Everything
Next.js isn’t just for frontends. With Server Actions and tRPC, you can ditch repetitive API routes.
Old: Creating /api/submit-form.js for form handling:
export default function handler(req, res) {
// Parse req.body, validate, save to DB...
}New: Server Actions in your components:
// app/contact/page.js
async function submitAction(formData) {
'use server';
// Validate, save to DB...
}
export default function Page() {
return <form action={submitAction}>...</form>;
}5. Sleepwalking Through Middleware
Middleware isn’t just for redirects. Use it for A/B testing, feature flags, or advanced authentication. But don’t overuse it—some logic belongs in the App Router.
Old: Redirecting users in getServerSideProps:
export async function getServerSideProps(context) {
if (!context.req.cookies.auth) {
return { redirect: { destination: '/login' } };
}
// ...
}New: Centralized middleware:
// middleware.js
export function middleware(request) {
if (request.nextUrl.pathname.startsWith('/dashboard')) {
return validateAuth(request);
}
}6. Not Using Turbopack (or Ignoring Tooling)
Still waiting minutes for next dev it to start? Turbopack (Next.js 13+) cuts local server startup and HMR times to milliseconds.
Old:
next devNew:
next dev --turbo7. Forgetting Streaming and Suspense
Blocking renders until all data loads? That’s so 2022. Stream content progressively.
Old: Single blocking data fetch:
export default function Page() {
const data = fetchData(); // Blocks render
return <div>{data}</div>;
}New: Stream UI chunks with Suspense:
export default function Page() {
return (
<div>
<Suspense fallback={<Skeleton />}>
<AsyncComponent />
</Suspense>
</div>
);
}What Modern Next.js Looks Like
The App Router Revolution
Layouts: Share UI between routes without remounting.
Server Components: Reduce client-side JS by default.
Metadata API: SEO-friendly and type-safe.
Built-In Performance
Turbopack: Faster dev server (up to 17x quicker updates).
ISR/On-Demand Revalidation: Content changes without full rebuilds.
Dynamic OG Images: Generate social cards at runtime.
Local Dev Nirvana
next dev --turbofor instant feedback.Fast Refresh with React Server Components.
TypeScript and ESLint are baked in.
The "Next.js Is Too Complex" Crowd
It’s not Next.js—it’s you.
You didn’t use RSC, so your client bundle is bloated.
You ignored the App Router, so routing feels clunky.
You stuck to client-side fetching, so your TTFB is glacial.
Next.js scales beautifully—if you use it like a 2024 framework.
TL;DR: Stop Using Next.js Like It’s 2022
Next.js isn’t just a React wrapper anymore. It’s a full-stack framework optimized for performance, SEO, and developer experience—if you ditch the 2022 mindset.
Embrace the App Router, adopt React Server Components, streamline APIs with Server Actions, use Turbopack, and, for the love of DX, stop blocking your renders.
Got a legacy Next.js habit you’ve kicked? Share it below. Let’s make 2022 practices extinct.
Cheers,
Prashant Adhikari
Next.js Evangelist & Performance Geek
P.S. If you’re still using getInitialProps, we need to talk.
