Introduction
Getting Started
- QuickStart
Patterns
- Languages
- Supported Languages
- Python
- Java
- JavaScript
- TypeScript
- Node.js
- React
- Fastify
- Next.js
- Terraform
- C#
- C++
- C
- Go
- Rust
- Swift
- React Native
- Spring Boot
- Kotlin
- Flutter
- Ruby
- PHP
- Scala
- Perl
- R
- Dart
- Elixir
- Erlang
- Haskell
- Lua
- Julia
- Clojure
- Groovy
- Fortran
- COBOL
- Pascal
- Assembly
- Bash
- PowerShell
- SQL
- PL/SQL
- T-SQL
- MATLAB
- Objective-C
- VBA
- ABAP
- Apex
- Apache Camel
- Crystal
- D
- Delphi
- Elm
- F#
- Hack
- Lisp
- OCaml
- Prolog
- Racket
- Scheme
- Solidity
- Verilog
- VHDL
- Zig
- MongoDB
- ClickHouse
- MySQL
- GraphQL
- Redis
- Cassandra
- Elasticsearch
- Security
- Performance
Integrations
- Code Repositories
- Team Messengers
- Ticketing
Enterprise
Next.js is a React framework that enables server-side rendering, static site generation, and other advanced features with zero configuration. It provides a powerful foundation for building production-ready React applications.
Next.js, despite its powerful features and optimizations, has several common anti-patterns that can lead to performance issues, maintenance problems, and poor user experience. Here are the most important anti-patterns to avoid when building Next.js applications.
// Anti-pattern: Using client-side data fetching for static content
function ProductPage({ productId }) {
const [product, setProduct] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchProduct() {
const res = await fetch(`/api/products/${productId}`);
const data = await res.json();
setProduct(data);
setLoading(false);
}
fetchProduct();
}, [productId]);
if (loading) return <div>Loading...</div>;
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
</div>
);
}
// Better approach: Use getStaticProps for static content
export async function getStaticProps({ params }) {
const res = await fetch(`https://api.example.com/products/${params.id}`);
const product = await res.json();
return {
props: {
product,
},
revalidate: 60, // Optional: Regenerate page after 60 seconds
};
}
export async function getStaticPaths() {
const res = await fetch('https://api.example.com/products');
const products = await res.json();
const paths = products.map((product) => ({
params: { id: product.id.toString() },
}));
return { paths, fallback: 'blocking' };
}
function ProductPage({ product }) {
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
</div>
);
}
Using client-side data fetching for content that could be pre-rendered leads to poor performance and SEO. Choose the appropriate data fetching method: getStaticProps
for static content, getServerSideProps
for dynamic content, and client-side fetching only for user-specific or frequently changing data.
// Anti-pattern: Using regular img tags
function ProductCard({ product }) {
return (
<div className="card">
<img
src={product.imageUrl}
alt={product.name}
width={300}
height={200}
/>
<h3>{product.name}</h3>
</div>
);
}
// Better approach: Use Next.js Image component
import Image from 'next/image';
function ProductCard({ product }) {
return (
<div className="card">
<Image
src={product.imageUrl}
alt={product.name}
width={300}
height={200}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,..."
priority={product.featured}
/>
<h3>{product.name}</h3>
</div>
);
}
Regular <img>
tags don’t benefit from Next.js’s automatic image optimization. Use the next/image
component to get automatic image optimization, lazy loading, and proper sizing.
// Anti-pattern: Using regular anchor tags
function Navigation() {
return (
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/products">Products</a>
</nav>
);
}
// Better approach: Use Next.js Link component
import Link from 'next/link';
function Navigation() {
return (
<nav>
<Link href="/">
<a>Home</a>
</Link>
<Link href="/about">
<a>About</a>
</Link>
<Link href="/products">
<a>Products</a>
</Link>
</nav>
);
}
// In Next.js 13+ with the App Router
import Link from 'next/link';
function Navigation() {
return (
<nav>
<Link href="/">Home</Link>
<Link href="/about">About</Link>
<Link href="/products">Products</Link>
</nav>
);
}
Regular anchor tags cause full page reloads. Use the next/link
component for client-side navigation between pages, which is faster and preserves state.
// Anti-pattern: Using API routes to fetch data for getStaticProps
// pages/products/[id].js
export async function getStaticProps({ params }) {
// Don't do this - it creates an unnecessary network request
const res = await fetch(`http://localhost:3000/api/products/${params.id}`);
const product = await res.json();
return {
props: { product },
};
}
// pages/api/products/[id].js
export default async function handler(req, res) {
const { id } = req.query;
const product = await getProductFromDatabase(id);
res.status(200).json(product);
}
// Better approach: Import the data fetching logic directly
// lib/products.js
export async function getProductById(id) {
return await getProductFromDatabase(id);
}
// pages/products/[id].js
import { getProductById } from '../../lib/products';
export async function getStaticProps({ params }) {
const product = await getProductById(params.id);
return {
props: { product },
};
}
// pages/api/products/[id].js
import { getProductById } from '../../../lib/products';
export default async function handler(req, res) {
const { id } = req.query;
const product = await getProductById(id);
res.status(200).json(product);
}
Using API routes to fetch data for getStaticProps
or getServerSideProps
creates unnecessary network requests. Import the data fetching logic directly in both server-side functions and API routes.
// Anti-pattern: Using getServerSideProps for semi-static content
export async function getServerSideProps() {
const res = await fetch('https://api.example.com/products');
const products = await res.json();
return {
props: { products },
};
}
// Better approach: Use Incremental Static Regeneration
export async function getStaticProps() {
const res = await fetch('https://api.example.com/products');
const products = await res.json();
return {
props: { products },
revalidate: 60, // Regenerate page after 60 seconds if requested
};
}
Using getServerSideProps
for content that doesn’t change frequently adds unnecessary server load. Use Incremental Static Regeneration (ISR) with revalidate
for content that changes occasionally.
// Anti-pattern: Loading large JavaScript libraries upfront
import LargeChart from 'heavy-chart-library';
function DashboardPage() {
return (
<div>
<h1>Dashboard</h1>
<LargeChart data={someData} />
</div>
);
}
// Better approach: Use dynamic imports and suspense
import dynamic from 'next/dynamic';
import { Suspense } from 'react';
const LargeChart = dynamic(() => import('heavy-chart-library'), {
loading: () => <div>Loading chart...</div>,
ssr: false, // Disable server-side rendering if the library isn't SSR compatible
});
function DashboardPage() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<div>Loading chart...</div>}>
<LargeChart data={someData} />
</Suspense>
</div>
);
}
Loading large JavaScript libraries upfront hurts performance metrics like Largest Contentful Paint (LCP) and Time to Interactive (TTI). Use dynamic imports, code splitting, and prioritize loading critical content first.
// Anti-pattern: Using plain JavaScript without type checking
function UserProfile({ user }) {
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
<p>{user.address.street}, {user.address.city}</p>
</div>
);
}
// Better approach: Use TypeScript
interface Address {
street: string;
city: string;
zipCode: string;
country: string;
}
interface User {
id: string;
name: string;
email: string;
address: Address;
}
interface UserProfileProps {
user: User;
}
function UserProfile({ user }: UserProfileProps) {
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
<p>{user.address.street}, {user.address.city}</p>
</div>
);
}
Not using TypeScript can lead to runtime errors and makes refactoring more difficult. Use TypeScript to catch errors at compile time and improve code maintainability.
// Anti-pattern: Duplicating layout code in every page
function HomePage() {
return (
<div>
<header>
<nav>{/* Navigation items */}</nav>
</header>
<main>
<h1>Welcome to our site</h1>
{/* Page content */}
</main>
<footer>
{/* Footer content */}
</footer>
</div>
);
}
function AboutPage() {
return (
<div>
<header>
<nav>{/* Navigation items */}</nav>
</header>
<main>
<h1>About Us</h1>
{/* Page content */}
</main>
<footer>
{/* Footer content */}
</footer>
</div>
);
}
// Better approach: Use layout components
function MainLayout({ children }) {
return (
<div>
<header>
<nav>{/* Navigation items */}</nav>
</header>
<main>{children}</main>
<footer>
{/* Footer content */}
</footer>
</div>
);
}
function HomePage() {
return (
<MainLayout>
<h1>Welcome to our site</h1>
{/* Page content */}
</MainLayout>
);
}
function AboutPage() {
return (
<MainLayout>
<h1>About Us</h1>
{/* Page content */}
</MainLayout>
);
}
// In Next.js 13+ with App Router
// app/layout.js
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<header>
<nav>{/* Navigation items */}</nav>
</header>
<main>{children}</main>
<footer>
{/* Footer content */}
</footer>
</body>
</html>
);
}
Duplicating layout code across pages leads to maintenance issues. Use layout components or the App Router’s layout system to share common UI elements across pages.
// Anti-pattern: Not configuring Next.js properly
// next.config.js
module.exports = {
// No configuration
};
// Better approach: Configure Next.js properly
// next.config.js
module.exports = {
images: {
domains: ['images.example.com'], // Allow images from this domain
formats: ['image/avif', 'image/webp'], // Specify image formats
},
i18n: {
locales: ['en', 'fr', 'es'],
defaultLocale: 'en',
},
async redirects() {
return [
{
source: '/old-blog/:slug',
destination: '/blog/:slug',
permanent: true,
},
];
},
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'X-Frame-Options',
value: 'DENY',
},
],
},
];
},
};
Not configuring Next.js properly means missing out on important optimizations and features. Use the Next.js config file to configure image domains, internationalization, redirects, headers, and other important settings.
// Anti-pattern: No error handling
function ProductPage({ product }) {
return (
<div>
<Header />
<ProductDetails product={product} /> {/* If this crashes, the whole page crashes */}
<RelatedProducts ids={product.relatedIds} />
<Footer />
</div>
);
}
// Better approach: Use error boundaries
import { ErrorBoundary } from 'react-error-boundary';
function ErrorFallback({ error, resetErrorBoundary }) {
return (
<div role="alert">
<p>Something went wrong:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
}
function ProductPage({ product }) {
return (
<div>
<Header />
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={() => {
// Reset the state of your app here
}}
>
<ProductDetails product={product} />
</ErrorBoundary>
<RelatedProducts ids={product.relatedIds} />
<Footer />
</div>
);
}
Without error boundaries, a runtime error in a component can break the entire page. Use error boundaries to gracefully handle errors and display fallback UIs.
// Anti-pattern: Using client components for everything in Next.js 13+
'use client';
import { useState } from 'react';
async function getData() {
const res = await fetch('https://api.example.com/data');
return res.json();
}
export default function Page() {
const [count, setCount] = useState(0);
const data = await getData(); // This will error in client components
return (
<div>
<h1>Data: {data.title}</h1>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
// Better approach: Split into server and client components
// Page.js (Server Component)
import { Counter } from './Counter';
async function getData() {
const res = await fetch('https://api.example.com/data');
return res.json();
}
export default async function Page() {
const data = await getData();
return (
<div>
<h1>Data: {data.title}</h1>
<Counter />
</div>
);
}
// Counter.js (Client Component)
'use client';
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
Using client components for everything in Next.js 13+ misses the benefits of server components. Use server components for data fetching and rendering static content, and client components only for interactive parts of your UI.
// Anti-pattern: Not implementing proper caching
// pages/products/[id].js
export async function getServerSideProps({ params }) {
const res = await fetch(`https://api.example.com/products/${params.id}`);
const product = await res.json();
return {
props: { product },
};
}
// Better approach: Implement proper caching
// pages/products/[id].js
export async function getStaticProps({ params }) {
const res = await fetch(`https://api.example.com/products/${params.id}`, {
headers: {
'Cache-Control': 'public, s-maxage=10, stale-while-revalidate=59',
},
});
const product = await res.json();
return {
props: { product },
revalidate: 60,
};
}
// In Next.js 13+ App Router
// app/products/[id]/page.js
export const revalidate = 60; // Revalidate at most every 60 seconds
async function getProduct(id) {
const res = await fetch(`https://api.example.com/products/${id}`, {
next: { revalidate: 60 },
});
return res.json();
}
export default async function ProductPage({ params }) {
const product = await getProduct(params.id);
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
</div>
);
}
Not implementing proper caching strategies can lead to unnecessary API calls and slower performance. Use appropriate caching headers and Next.js’s built-in caching mechanisms like ISR and the fetch API’s cache options.