Skip to main content

Next.js Component Basics

Introduction

Components are the building blocks of Next.js applications. As Next.js is built on top of React, it uses the same component-based architecture that makes UI development modular, reusable, and maintainable. Understanding how components work in Next.js is essential for building modern web applications.

In this guide, we'll explore the basics of components in Next.js, how they differ from traditional React components, and best practices for creating and using them effectively.

What Are Components?

Components in Next.js are reusable pieces of code that return a React element representing a part of the user interface. They allow you to:

  • Split the UI into independent, reusable pieces
  • Think about each piece in isolation
  • Build complex UIs from simple building blocks
  • Maintain and reuse code more efficiently

Creating Your First Component

Let's create a simple component in Next.js:

jsx
// components/Button.jsx
function Button({ text, onClick }) {
return (
<button
onClick={onClick}
className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
>
{text}
</button>
);
}

export default Button;

To use this component in a page:

jsx
// app/page.js
import Button from '../components/Button';

export default function HomePage() {
return (
<div className="p-8">
<h1 className="text-2xl font-bold mb-4">Welcome to My Next.js App</h1>
<Button
text="Click Me"
onClick={() => alert('Button clicked!')}
/>
</div>
);
}

Component Types in Next.js

Next.js 13+ introduced the App Router which supports two types of components:

1. Server Components (Default)

Server Components are a new type of component that renders on the server. They:

  • Can fetch data directly
  • Access backend resources directly
  • Keep sensitive data and logic on the server
  • Reduce client-side JavaScript

Example of a Server Component:

jsx
// app/users/page.js
async function UsersPage() {
// This data fetching happens on the server
const response = await fetch('https://jsonplaceholder.typicode.com/users');
const users = await response.json();

return (
<div>
<h1>Users</h1>
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}

export default UsersPage;

2. Client Components

Client Components render on the client and enable:

  • Interactivity and event listeners
  • Use of React hooks
  • Access to browser APIs
  • Maintaining state

To create a Client Component, add the 'use client' directive at the top of your file:

jsx
'use client'

// components/Counter.jsx
import { useState } from 'react';

function Counter() {
const [count, setCount] = useState(0);

return (
<div className="p-4 border rounded">
<p className="mb-2">Count: {count}</p>
<button
onClick={() => setCount(count + 1)}
className="bg-green-500 text-white px-3 py-1 rounded mr-2"
>
Increment
</button>
<button
onClick={() => setCount(count - 1)}
className="bg-red-500 text-white px-3 py-1 rounded"
>
Decrement
</button>
</div>
);
}

export default Counter;

Component Props

Props are the inputs to your components. They allow you to pass data from a parent component to a child component.

jsx
// components/Card.jsx
function Card({ title, description, imageUrl }) {
return (
<div className="border rounded-lg overflow-hidden shadow-md">
{imageUrl && (
<img
src={imageUrl}
alt={title}
className="w-full h-48 object-cover"
/>
)}
<div className="p-4">
<h3 className="text-xl font-semibold">{title}</h3>
<p className="mt-2 text-gray-600">{description}</p>
</div>
</div>
);
}

export default Card;

Using the Card component with props:

jsx
// app/page.js
import Card from '../components/Card';

export default function HomePage() {
return (
<div className="p-8 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<Card
title="Getting Started with Next.js"
description="Learn how to build apps with the Next.js framework."
imageUrl="/images/nextjs.png"
/>
<Card
title="React Fundamentals"
description="Master the basics of React for frontend development."
imageUrl="/images/react.png"
/>
<Card
title="Styling in Next.js"
description="Different ways to style your Next.js applications."
/>
</div>
);
}

Component Composition

Component composition is a powerful pattern where you combine smaller, focused components to build more complex UIs.

Let's create a layout component that uses composition:

jsx
// components/Layout.jsx
import Navbar from './Navbar';
import Footer from './Footer';

function Layout({ children }) {
return (
<div className="min-h-screen flex flex-col">
<Navbar />
<main className="flex-grow container mx-auto px-4 py-8">
{children}
</main>
<Footer />
</div>
);
}

export default Layout;

Now you can wrap your page content with this layout:

jsx
// app/about/page.js
import Layout from '../../components/Layout';

export default function AboutPage() {
return (
<Layout>
<h1 className="text-3xl font-bold mb-4">About Us</h1>
<p>Welcome to our website. We're dedicated to teaching Next.js!</p>
</Layout>
);
}

Best Practices for Next.js Components

  1. Keep components small and focused: Each component should do one thing well.

  2. Use the right component type:

    • Use Server Components for data fetching and static content
    • Use Client Components when you need interactivity
  3. Move Client Components down the tree:

    jsx
    // Good: Only make interactive parts client components
    // app/page.js (Server Component)
    import InteractiveForm from '../components/InteractiveForm';

    export default function Page() {
    return (
    <div>
    <h1>Static Content</h1>
    <p>This renders on the server</p>
    <InteractiveForm /> {/* This is a Client Component */}
    </div>
    );
    }
  4. Create a components folder structure:

    components/
    ├── ui/ # Reusable UI components
    │ ├── Button.jsx
    │ └── Card.jsx
    ├── layout/ # Layout components
    │ ├── Navbar.jsx
    │ └── Footer.jsx
    └── features/ # Feature-specific components
    └── auth/
    ├── LoginForm.jsx
    └── SignupForm.jsx

Real-World Example: Building a Comment Section

Let's build a more complex example that demonstrates multiple concepts:

jsx
// components/comments/CommentList.jsx (Server Component)
async function CommentList({ postId }) {
// Fetch comments on the server
const comments = await fetchComments(postId);

return (
<div className="mt-8">
<h2 className="text-xl font-bold mb-4">Comments ({comments.length})</h2>
{comments.map(comment => (
<Comment key={comment.id} comment={comment} />
))}
<AddCommentForm postId={postId} />
</div>
);
}

// Server-side data fetching function
async function fetchComments(postId) {
// In a real app, this would fetch from your database or API
return [
{ id: 1, author: "Jane Doe", text: "Great article!", date: "2023-07-15" },
{ id: 2, author: "John Smith", text: "I learned a lot, thanks!", date: "2023-07-16" }
];
}

export default CommentList;
jsx
// components/comments/Comment.jsx
function Comment({ comment }) {
const { author, text, date } = comment;

return (
<div className="border-b pb-4 mb-4">
<div className="flex justify-between items-center mb-2">
<h3 className="font-semibold">{author}</h3>
<span className="text-sm text-gray-500">{date}</span>
</div>
<p className="text-gray-700">{text}</p>
</div>
);
}

export default Comment;
jsx
// components/comments/AddCommentForm.jsx
'use client'

import { useState } from 'react';

function AddCommentForm({ postId }) {
const [author, setAuthor] = useState('');
const [text, setText] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);

const handleSubmit = async (e) => {
e.preventDefault();
setIsSubmitting(true);

try {
// In a real app, this would submit to your API
await new Promise(resolve => setTimeout(resolve, 1000));

// Reset form
setAuthor('');
setText('');
alert('Comment submitted successfully!');
} catch (error) {
alert('Failed to submit comment');
} finally {
setIsSubmitting(false);
}
};

return (
<form onSubmit={handleSubmit} className="mt-6 bg-gray-50 p-4 rounded">
<h3 className="text-lg font-medium mb-4">Add a Comment</h3>

<div className="mb-4">
<label htmlFor="author" className="block mb-1">Name</label>
<input
id="author"
type="text"
value={author}
onChange={(e) => setAuthor(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>

<div className="mb-4">
<label htmlFor="text" className="block mb-1">Comment</label>
<textarea
id="text"
value={text}
onChange={(e) => setText(e.target.value)}
className="w-full px-3 py-2 border rounded"
rows={3}
required
/>
</div>

<button
type="submit"
disabled={isSubmitting}
className="bg-blue-500 text-white px-4 py-2 rounded disabled:bg-blue-300"
>
{isSubmitting ? 'Submitting...' : 'Post Comment'}
</button>
</form>
);
}

export default AddCommentForm;

Using the comments section in a blog post page:

jsx
// app/blog/[slug]/page.js
import CommentList from '../../../components/comments/CommentList';

async function BlogPostPage({ params }) {
const { slug } = params;
const post = await fetchPostBySlug(slug);

return (
<article className="max-w-3xl mx-auto py-8">
<h1 className="text-4xl font-bold mb-4">{post.title}</h1>
<div className="prose lg:prose-xl">
{post.content}
</div>

<CommentList postId={post.id} />
</article>
);
}

async function fetchPostBySlug(slug) {
// In a real app, this would fetch from your database or API
return {
id: 123,
title: "Understanding Next.js Components",
content: "This is a blog post about Next.js components...",
publishedAt: "2023-07-14"
};
}

export default BlogPostPage;

Summary

In this guide, we explored the fundamentals of components in Next.js:

  • Components are the building blocks of Next.js applications
  • Next.js supports both Server Components and Client Components
  • Server Components render on the server and are ideal for fetching data
  • Client Components render on the client and are used for interactivity
  • Props allow components to receive data from their parents
  • Component composition helps create complex UIs from simple pieces
  • Proper organization and best practices lead to maintainable code

By mastering component basics in Next.js, you've taken a significant step toward building modern, efficient web applications. Components make your code reusable, testable, and easier to maintain as your application grows.

Additional Resources

Exercises

  1. Basic Component Creation: Create a responsive Card component that displays a product with an image, title, price, and "Add to Cart" button.

  2. Component Composition: Build a ProductGrid component that displays multiple Product cards in a responsive grid layout.

  3. Server Component Practice: Create a page that fetches and displays a list of posts from the JSONPlaceholder API using a Server Component.

  4. Client Component Exercise: Build a Tabs component that allows users to toggle between different content sections.

  5. Combined Application: Create a simple blog page that uses Server Components for fetching content and Client Components for interactive elements like a comment form or like button.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)