After working with React and Next.js for over 5 years across multiple production applications serving millions of users, I've learned some hard lessons. Here are 10 best practices that would have saved me countless hours of debugging and refactoring.
1. Master Server Components in Next.js 14+
Server Components are a game-changer for performance. By default, components in the app directory are Server Components.
// app/dashboard/page.tsx
async function Dashboard() {
const data = await fetchData(); // Runs on server
return <div>{data}</div>;
}
Why it matters: Reduces bundle size and improves initial load time.
2. Use React.memo Wisely
Don't wrap every component with React.memo. Use it only when:
- Component renders often with same props
- Component is computationally expensive
const ExpensiveComponent = React.memo(({ data }) => {
// Heavy computation here
return <div>{processData(data)}</div>;
});
3. Implement Proper Error Boundaries
Always have error boundaries in production apps:
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <FallbackUI />;
}
return this.props.children;
}
}
4. Optimize Images with Next.js Image Component
import Image from 'next/image';
<Image
src="/hero.jpg"
width={800}
height={600}
alt="Hero"
priority // For above-fold images
/>
5. Use SWR or React Query for Data Fetching
Stop managing loading/error states manually:
import useSWR from 'swr';
function Profile() {
const { data, error, isLoading } = useSWR('/api/user', fetcher);
if (isLoading) return <Spinner />;
if (error) return <Error />;
return <div>{data.name}</div>;
}
6. Implement Proper TypeScript Types
Don't use 'any' - invest time in proper typing:
interface User {
id: string;
name: string;
email: string;
}
const UserCard: React.FC<{ user: User }> = ({ user }) => {
return <div>{user.name}</div>;
};
7. Use Dynamic Imports for Code Splitting
Reduce initial bundle size:
import dynamic from 'next/dynamic';
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
loading: () => <Spinner />,
ssr: false
});
8. Leverage Next.js Middleware for Auth
// middleware.ts
export function middleware(request: NextRequest) {
const token = request.cookies.get('token');
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
}
9. Use React Hook Form for Forms
Stop managing form state manually:
import { useForm } from 'react-hook-form';
function ContactForm() {
const { register, handleSubmit, formState: { errors } } = useForm();
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('email', { required: true })} />
{errors.email && <span>Required</span>}
</form>
);
}
10. Implement Proper SEO with Metadata API
// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }) {
const post = await getPost(params.slug);
return {
title: post.title,
description: post.excerpt,
openGraph: {
images: [post.image],
},
};
}
Conclusion
These practices have significantly improved my code quality and app performance. Start implementing them one at a time, and you'll see the difference.
What are your favorite React/Next.js practices? Share in the comments!
Currently working on Quran.com serving 50M+ users. Follow me for more web development insights!
Top comments (0)