Skip to content

Instantly share code, notes, and snippets.

@SH20RAJ
Last active August 21, 2025 06:19
Show Gist options
  • Save SH20RAJ/7ac76de07a6eb84e5ea1a9fde1da8950 to your computer and use it in GitHub Desktop.
Save SH20RAJ/7ac76de07a6eb84e5ea1a9fde1da8950 to your computer and use it in GitHub Desktop.
Optimising NextJS

Best Practices for Next.js Development (2025 Edition)

Next.js has become the go-to framework for building modern web apps. But as projects grow, it’s easy to fall into messy structures, performance issues, and technical debt.

This guide covers best practices for Next.js development — from project structure to data fetching, state management, testing, security, and deployment.


1. Project Structure & Organization

✅ Use a clear folder structure

For App Router (Next.js 13+):

/app
  /dashboard
    /page.tsx
    /layout.tsx
  /blog
    /[slug]
      /page.tsx
  /api
    /route.ts
/components
/lib
/hooks
/styles
  • /app → routes, layouts, server & client components
  • /components → reusable UI components
  • /lib → utilities (db, auth, API wrappers)
  • /hooks → custom React hooks
  • /styles → global styles, Tailwind configs

✅ Co-locate components & tests

  • Keep related code together:
/components
  /Button
    Button.tsx
    Button.test.tsx

2. Component Best Practices

✅ Prefer Server Components

  • Server Components (RSC) reduce bundle size, remove the need for useEffect in many cases, and improve performance.
// Server Component (default)
export default async function Page() {
  const data = await getData();
  return <div>{data.title}</div>;
}

✅ Use Client Components only when needed

  • Add "use client" at the top only for interactive components.
"use client";
import { useState } from "react";

export function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

✅ Keep Components Small & Reusable

  • Split large UI chunks into smaller parts.
  • Favor composition over props explosion.

3. Data Fetching & State Management

✅ Use fetch with Caching in App Router

export default async function Page() {
  const res = await fetch("https://api.example.com/data", {
    next: { revalidate: 3600 }, // ISR cache for 1 hour
  });
  const data = await res.json();
  return <div>{data.title}</div>;
}
  • Use revalidate for ISR.
  • Use cache: "no-store" for always-fresh data.

✅ Handle Global State Wisely

  • For server state → prefer React Query, SWR, or Next.js fetch.
  • For client state → use Zustand or Jotai instead of Redux unless needed.

✅ Avoid Over-Fetching

  • Consolidate queries in Server Components.
  • Use batching (e.g., GraphQL or tRPC).

4. Performance Best Practices

  • Use <Image /> for automatic image optimization.
  • Use next/font for fonts → avoids layout shifts.
  • Dynamic imports for heavy components:
import dynamic from "next/dynamic";
const Chart = dynamic(() => import("./Chart"), { ssr: false });
  • Bundle Analysis → Find large dependencies:
npm install @next/bundle-analyzer

5. API Routes & Backend Best Practices

  • Use /app/api/route.ts for backend logic.
  • Keep business logic in /lib so it’s reusable.
// app/api/users/route.ts
import { db } from "@/lib/db";

export async function GET() {
  const users = await db.user.findMany();
  return Response.json(users);
}

✅ Edge & Serverless Functions

  • Use runtime: "edge" for lightweight APIs (e.g., auth, geolocation).
export const config = { runtime: "edge" };

6. Security Best Practices

  • Store secrets in environment variables (.env.local).
  • Use HTTP-only cookies for authentication tokens.
  • Sanitize user input (e.g., with DOMPurify).
  • Use Helmet headers (or next-safe) for CSP, XSS protection.
npm install next-safe
import { createMiddleware } from "next-safe";
export default createMiddleware({
  contentSecurityPolicy: { "script-src": ["'self'"] },
});

7. Testing & Quality Assurance

✅ Use TypeScript Strict Mode

// tsconfig.json
"strict": true,
"forceConsistentCasingInFileNames": true,
"noUnusedLocals": true

✅ Write Component Tests with Jest/RTL

import { render, screen } from "@testing-library/react";
import Button from "./Button";

test("renders button", () => {
  render(<Button>Click</Button>);
  expect(screen.getByText("Click")).toBeInTheDocument();
});

✅ E2E Tests with Playwright or Cypress

npx playwright test

8. SEO & Accessibility

  • Use Next.js Metadata API:
export async function generateMetadata() {
  return {
    title: "My Next.js Site",
    description: "Best practices in action",
  };
}
  • Add OpenGraph & Twitter cards.
  • Ensure semantic HTML & ARIA labels for accessibility.

9. Deployment Best Practices

  • Deploy on Vercel for ISR, CDN caching, and edge functions.
  • Use Preview Deployments for QA.
  • Enable logging & monitoring (Sentry, Logtail).

10. Developer Workflow

  • ESLint + Prettier for consistent code.
  • Husky + lint-staged for pre-commit checks.
  • CI/CD pipelines (GitHub Actions / Vercel Integrations).
// package.json
"lint-staged": {
  "*.ts": "eslint --fix",
  "*.tsx": "eslint --fix"
}

Final Checklist: Best Next.js Practices

✅ Organize project structure clearly ✅ Prefer Server Components, use Client only when needed ✅ Use ISR & caching for data fetching ✅ Optimize images, fonts, and bundles ✅ Secure secrets & user data ✅ Write tests (unit + E2E) ✅ Use Metadata API for SEO ✅ Deploy with CDN + monitoring ✅ Enforce coding standards with ESLint/Prettier


By following these practices, your Next.js apps will be faster, more secure, easier to maintain, and ready for scale 🚀

You want a context file (like ai.context.json, .cursor/context, or ai_rules.md) that you can drop into AI-powered IDEs (Cursor, Windsurf, Copilot Chat, etc.).

This file acts as a persistent brain for the IDE — it tells the AI how to code in your projects so it always follows scalable, optimized, and maintainable Next.js best practices.

Here’s a ready-to-use version 👇


📂 ai.context.md (for AI IDEs)

# 🔧 AI IDE Context for Next.js Development

You are coding inside a **Next.js 15+ App Router** project.  
Always write **scalable, optimized, maintainable, and production-ready code**.  

---

## 📦 Project Architecture
- Use **App Router (`/app`)** with `page.tsx` and `layout.tsx`.
- Store reusable UI in `/components`, hooks in `/hooks`, business logic in `/lib`, global styles in `/styles`.
- Co-locate tests (`Component.tsx` + `Component.test.tsx`).
- Enforce **TypeScript strict mode**.
- Keep files small and modular.

---

## 🧩 Component Rules
- Prefer **Server Components** by default.  
- Use `"use client"` only for interactive components.  
- Split large components → smaller reusable ones.  
- Use semantic HTML + accessibility (ARIA where needed).  
- Always include `alt` attributes on images.  

---

## ⚡ Data Fetching
- Use **React Server Components** with async/await.  
- Use caching properly:  
  - `next: { revalidate: 60 }` for ISR  
  - `cache: "no-store"` for fresh data  
- Keep business logic in `/lib`, not directly in components.  
- Avoid over-fetching (batch or colocate queries).  

---

## 🚀 Performance
- Always use `<Image />` for images.  
- Use `next/font` for fonts.  
- Use **dynamic imports** for heavy/rarely used components.  
- Tree-shake large libraries (import only needed functions).  
- Run bundle analysis before production.  

---

## 🔐 Security
- Secrets → `process.env` (never hardcoded).  
- Auth → use **HTTP-only cookies**, not localStorage.  
- Sanitize user input (e.g., DOMPurify).  
- Add **CSP + security headers** (`next-safe`).  
- Don’t leak internal server errors to clients.  

---

## 🧪 Testing
- Write unit tests with **Jest + RTL**.  
- Write E2E tests with **Playwright**.  
- Add Husky + lint-staged for pre-commit checks.  
- Enforce **ESLint + Prettier**.  

---

## 🌍 SEO & Accessibility
- Use **Next.js Metadata API** for title, description, OpenGraph, and Twitter cards.  
- Add **structured data (JSON-LD)** where relevant.  
- Ensure full accessibility (keyboard nav, ARIA).  

---

## ☁️ Deployment
- Target **Vercel** or CDN-backed hosting.  
- Use ISR for semi-dynamic content.  
- Offload light APIs to **Edge Functions**.  
- Monitor with Sentry/Datadog.  
- Use preview deployments for QA.  

---

## 📖 Developer Workflow
- Follow **Conventional Commits**.  
- Use CI/CD pipelines with automated tests.  
- Add inline comments for tricky logic.  
- Document components & utilities.  
- Keep README updated.  

---

## ✅ Your Role
When generating or modifying code:
- Always follow these rules.  
- Suggest improvements when something is unoptimized.  
- Ensure **performance, maintainability, and scalability**.  
- Provide **production-ready, clean code**.  

👉 You can drop this into:

  • .cursor/context (for Cursor IDE)
  • ai.context.md in root (for Windsurf/others)
  • Or as a system prompt in Copilot Chat

Eliminating Hydration Flicker in React & Next.js: A Practical Guide

Modern web apps often suffer from a subtle but annoying problem: flickering UI after hydration.

You’ve probably noticed it—loading spinners, buttons or profile pictures flashing for a moment before snapping into the “real” state. This happens on Instagram, Best Buy, and almost every site you use. Let’s break down why this happens, and how to fix it with some optimization techniques.


The Problem: Why Websites Flicker

A web page goes through several phases before the user sees the final content:

  1. Build time (static generation) – Pages are pre-rendered with limited info (no user-specific data).
  2. Request time (server rendering) – The server can fetch user/auth data, but this slows down response.
  3. Paint – Browser shows the first HTML from the server.
  4. Hydration – React (or other framework) takes over the HTML and makes it interactive.
  5. Async client data load – API calls run, loading spinners show, and UI updates.

⚡ The flicker happens when the HTML shown at paint doesn’t match what React renders at hydration.

Example:

  • The server sends HTML with a “Login” button (doesn’t know if you’re logged in).
  • React runs, fetches user info, and suddenly swaps “Login” for your profile picture.
  • Result: a noticeable flicker.

Example 1: The Broken Clock

A clock rendered at build time will show a stale second hand until React hydrates:

// BrokenClock.jsx
"use client";
import { useEffect, useState } from "react";

export default function BrokenClock() {
  const [date, setDate] = useState(new Date());

  useEffect(() => {
    const interval = setInterval(() => setDate(new Date()), 1000);
    return () => clearInterval(interval);
  }, []);

  const seconds = date.getSeconds() * 6; // 6° per second
  return <div>🕒 Second hand: {seconds}°</div>;
}

On reload, you’ll see a flicker:

  • Build HTML shows the wrong time.
  • Hydration updates it to the correct time.

Solution 1: Fix with Inline Script (Pre-Paint Update)

We can correct the DOM before first paint using a script that runs before React hydrates.

<div id="clock">🕒 Second hand: 0°</div>

<script>
  const now = new Date();
  const seconds = now.getSeconds() * 6;
  document.getElementById("clock").innerText = `🕒 Second hand: ${seconds}°`;
</script>

By the time React hydrates, the clock is already correct—no flicker.


Example 2: Input Wiping Bug

React sometimes wipes user input on hydration:

// BrokenInput.jsx
"use client";
import { useState } from "react";

export default function BrokenInput() {
  const [value, setValue] = useState(""); // starts empty

  return (
    <input
      value={value}
      onChange={(e) => setValue(e.target.value)}
      placeholder="Type here..."
    />
  );
}

💥 Problem: If a user types before hydration finishes, React resets the input to "".


Solution 2: Initialize State from the DOM

We can read the DOM’s value before hydration to avoid wiping:

// FixedInput.jsx
"use client";
import { useState } from "react";

export default function FixedInput() {
  const [value, setValue] = useState(
    () => document.getElementById("fixed-input")?.value || ""
  );

  return (
    <input
      id="fixed-input"
      defaultValue=""
      value={value}
      onChange={(e) => setValue(e.target.value)}
      placeholder="Type here..."
    />
  );
}

Now React initializes with what the user typed in, avoiding resets.


Example 3: Profile Picture Flicker

Most sites (e.g., Twitter, Instagram) flicker between Login button → Profile pic.

Broken approach:

HTML at build time:

<div id="auth">Login</div>

React replaces it after hydration:

<div id="auth">
  <img src="/profile.jpg" />
</div>

Fixed approach: Use Cookie + Inline Script

Store a non-HTTP-only cookie with the profile picture URL.

<div id="auth">Login</div>

<script>
  const cookie = document.cookie.split("; ")
    .find(row => row.startsWith("profilePic="));
  if (cookie) {
    const url = cookie.split("=")[1];
    document.getElementById("auth").innerHTML = `<img src="${url}" />`;
  }
</script>

✅ Now the profile picture shows instantly at first paint. When React hydrates, it picks up without flicker.


Best Practices for Optimization

  • Don’t overuse inline scripts – Reserve for critical UI (auth, profile pics, clocks).
  • Prefer cached/static data – Use browser cache & cookies for instant paint.
  • Balance SSR vs Static – Static generation is faster, SSR gives request-specific data. Combine wisely.
  • Aim for a perfect first paint – What the user first sees should match what React will render.

Final Thoughts

Hydration flickers are everywhere, but with a little care, you can eliminate them.

  • Use inline DOM updates before paint for instant correctness.
  • Initialize React state from existing DOM values.
  • Leverage cookies or cached metadata for authentication states.

By doing this, your site feels smoother, faster, and more professional—without spinners or flashing UI.

https://www.youtube.com/watch?v=wski7bnpW8Y&ab_channel=EthanNiser

The Ultimate Guide to Next.js Optimization (2025 Edition)

Next.js is one of the most powerful frameworks for building full-stack React applications. But as projects grow, performance, bundle size, and scalability become bottlenecks.

This guide covers everything you need to know about optimizing a Next.js app — from build strategies to runtime improvements, caching, SEO, and deployment best practices.


1. Optimize Rendering Strategies

✅ Use Static Site Generation (SSG) when possible

  • Pages that don’t change often should be pre-rendered at build time.
  • Example: Blog posts, documentation, landing pages.
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
  const posts = await getAllPosts();
  return posts.map((post) => ({ slug: post.slug }));
}

Benefits:

  • Served via CDN (super fast).
  • Zero server cost at runtime.

✅ Incremental Static Regeneration (ISR) for semi-dynamic content

  • Use revalidate to keep static pages fresh without rebuilding the whole app.
export async function generateMetadata() {
  return {
    revalidate: 60, // Rebuild every 60 seconds
  };
}

Best for: e-commerce product pages, marketing pages with frequent updates.


✅ Server-Side Rendering (SSR) only when needed

  • Use SSR only for pages requiring request-specific data (auth, geolocation, personalization).
export default async function Page({ params }) {
  const data = await getData(params.id); // runs on every request
  return <div>{data.title}</div>;
}

⚠️ Downside: Increases TTFB (time to first byte).


✅ Client-Side Rendering (CSR) for highly interactive parts

  • Use when SEO is not critical (dashboards, logged-in user features).
  • Combine CSR with Suspense & loading states.
"use client";
import { useEffect, useState } from "react";

export default function Dashboard() {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch("/api/data").then((res) => res.json()).then(setData);
  }, []);

  return <div>{data ? data.title : "Loading..."}</div>;
}

2. Reduce JavaScript Bundle Size

✅ Code Splitting & Dynamic Imports

Next.js supports dynamic imports out of the box.

import dynamic from "next/dynamic";

const Chart = dynamic(() => import("./Chart"), { ssr: false });

export default function Page() {
  return <Chart />;
}

This ensures the Chart component loads only when needed.


✅ Tree Shaking

  • Import only what you use.
  • Example: Instead of import _ from "lodash", import import debounce from "lodash/debounce";

✅ Avoid Large Dependencies in Client Bundles

  • Heavy libraries (like moment.js) should be replaced (date-fns, luxon).
  • Use server-only imports with "use server".
"use server";
import { db } from "@/lib/db"; // won't be sent to client

3. Data Fetching Optimizations

✅ Use React Server Components (RSCs) in Next.js App Router

  • Data fetching moves to the server → smaller client bundles.
  • Example:
// app/products/page.tsx
export default async function ProductsPage() {
  const products = await fetch("https://api.example.com/products").then(r => r.json());
  return <ProductList products={products} />;
}

Client never downloads fetch logic → faster page loads.


✅ Cache API Responses

  • Use fetch caching options in App Router:
await fetch("https://api.example.com/data", {
  next: { revalidate: 3600 }, // cache for 1 hour
});
  • For SSR APIs, use Redis / Vercel KV to cache DB queries.

4. Optimizing Images & Fonts

✅ Use Next.js <Image />

  • Built-in image optimization (resizing, lazy-loading).
import Image from "next/image";

<Image src="/hero.png" alt="Hero" width={800} height={600} priority />;

Features:

  • Responsive sizes (srcSet).
  • Automatic WebP conversion.
  • Lazy loading.

✅ Optimize Fonts

  • Use next/font for Google Fonts or custom fonts.
import { Inter } from "next/font/google";
const inter = Inter({ subsets: ["latin"] });

export default function Layout({ children }) {
  return <body className={inter.className}>{children}</body>;
}

No layout shifts, no external CSS fetch.


5. Improve Caching & Performance

✅ Use HTTP Caching

  • Static assets (JS, CSS, images) → CDN with long cache.
  • API responses → use Cache-Control headers.

✅ Edge Functions for Personalization

  • Run lightweight logic close to users.
  • Example: Geolocation or A/B testing at the edge.
export const config = { runtime: "edge" };

6. Avoid Hydration Mismatches & Flicker

  • Always ensure server HTML matches client state.
  • Use cookies/localStorage pre-paint tricks (like profile pic example).
  • Use suppressHydrationWarning only as a last resort.
<div suppressHydrationWarning>
  {isDarkMode ? "🌙" : "☀️"}
</div>

7. SEO Optimizations

  • Use Next.js Metadata API (generateMetadata).
  • Optimize OpenGraph & Twitter cards.
  • Enable structured data with <script type="application/ld+json">.
export async function generateMetadata() {
  return {
    title: "My Page",
    description: "Optimized with Next.js",
    openGraph: { images: ["/og-image.png"] },
  };
}

8. Deployment Best Practices

✅ Use Vercel (or CDN-backed hosting)

  • Pages are deployed globally at the edge.
  • ISR is automatically handled.

✅ Use Middleware for Auth & Redirects

// middleware.ts
import { NextResponse } from "next/server";

export function middleware(req) {
  const token = req.cookies.get("token");
  if (!token) return NextResponse.redirect("/login");
}

✅ Monitor & Analyze Performance

  • Use Next.js Analytics (next/script)
  • Enable Web Vitals tracking.

9. Database & API Layer Optimization

  • Prefer Edge DBs (PlanetScale, Neon, Vercel KV).
  • Batch queries to avoid N+1 issues.
  • Use Drizzle ORM / Prisma for optimized queries.
  • Apply API rate-limiting & caching.

10. Developer Experience Optimizations

  • ESLint & Prettier for consistent code.
  • Bundle Analyzer to find large dependencies:
npm install @next/bundle-analyzer

next.config.js:

const withBundleAnalyzer = require("@next/bundle-analyzer")();
module.exports = withBundleAnalyzer({});

Final Checklist for Next.js Optimization

✅ Use SSG/ISR wherever possible ✅ Cache aggressively (static assets, API data) ✅ Optimize images & fonts with Next.js built-ins ✅ Minimize JavaScript (dynamic imports, tree shaking) ✅ Use React Server Components for data fetching ✅ Deploy globally on edge/CDN ✅ Monitor performance & bundle size


With these optimizations, your Next.js application will be blazing fast, scalable, and SEO-friendly — ready for production at any scale.

🛠️ Next.js Scalable & Maintainable Development Prompt

You are an expert Next.js 15+ senior software engineer, specializing in building
large-scale, highly performant, maintainable, and production-ready applications.  

When writing or refactoring code, always follow these principles:  

## 🚀 Project & Codebase Architecture
- Use the **App Router** (`/app`) and organize routes clearly with `page.tsx` and `layout.tsx`.
- Keep reusable UI in `/components`, business logic in `/lib`, hooks in `/hooks`, and global styles in `/styles`.
- Co-locate tests with components when possible (`Component.tsx` + `Component.test.tsx`).
- Use TypeScript everywhere with **strict mode enabled** (`strict: true` in tsconfig).
- Avoid monolithic files; keep functions small, modular, and testable.

## 🧩 Component Best Practices
- **Prefer Server Components** by default (smaller bundles, no client JS).
- Mark interactive components explicitly with `"use client"`.
- Split large components into smaller reusable ones.
- Use composition patterns instead of prop-heavy components.
- Follow accessibility best practices (semantic HTML, ARIA where needed).

## 📦 Data Fetching & State Management
- Use `fetch` inside Server Components with proper caching:
  - `next: { revalidate: 60 }` for ISR
  - `cache: "no-store"` for real-time data
- For global client state, prefer **Zustand/Jotai** over Redux unless complex workflows require it.
- Avoid over-fetching — batch API calls or colocate queries in Server Components.
- For API logic, use `/app/api/route.ts` with reusable utilities in `/lib`.

## ⚡ Performance Optimizations
- Always use `<Image />` for images (lazy loading, responsive, CDN).
- Always use `next/font` for fonts (no CLS, optimized delivery).
- Apply **dynamic imports** for heavy client-only dependencies:
  ```ts
  const Chart = dynamic(() => import("./Chart"), { ssr: false });
  • Run bundle analysis (@next/bundle-analyzer) to prevent bloated builds.
  • Tree-shake and import only what is needed (e.g., lodash/debounce instead of full lodash).

🔐 Security Best Practices

  • Never expose secrets; always load from process.env.
  • Use HTTP-only cookies for tokens (never localStorage).
  • Sanitize user input with libraries like DOMPurify when handling raw HTML.
  • Add CSP & security headers (e.g., next-safe).
  • Avoid leaking internal errors; send generic error messages to clients.

🧪 Testing & Quality

  • Use Jest/RTL for unit/component tests and Playwright/Cypress for E2E.
  • Always write tests for critical paths: auth, payments, API routes.
  • Enforce ESLint + Prettier for consistent formatting.
  • Add Husky + lint-staged to run checks pre-commit.

🌍 SEO & Accessibility

  • Use the Next.js Metadata API for title, description, and OpenGraph tags.
  • Include JSON-LD structured data for rich results.
  • Ensure semantic HTML and keyboard navigation.
  • Use <Image alt=""> for accessibility.

☁️ Deployment & Scaling

  • Deploy on Vercel or CDN-backed platforms for global edge delivery.
  • Use ISR and caching strategies to reduce server load.
  • Split critical APIs into edge functions for low latency.
  • Add monitoring & error tracking (Sentry, Logtail, Datadog).
  • Enable preview deployments for QA.

📖 Developer Workflow

  • Commit messages should follow Conventional Commits.
  • Use GitHub Actions or CI/CD for automated tests & builds.
  • Document all components and utilities with JSDoc/MDX.
  • Keep README.md updated with setup instructions & architecture notes.

✅ Your Role

Whenever you generate code:

  • Always default to scalable, optimized, and maintainable patterns.
  • Ensure consistency with Next.js best practices above.
  • Optimize for performance, DX, security, and SEO.
  • Add inline comments for tricky logic.
  • Suggest improvements when you notice inefficiencies.

If asked for code, provide production-ready code — modular, tested, and following these rules.


---

This acts like a **framework-level brain implant** for Cursor or Copilot:  
- Guides them to **always default to best practices**  
- Covers **architecture → components → data → performance → security → testing → deployment**  

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment