What is Next.js?
Next.js is a React-based web development framework that complements many features missing in React. Developed by Vercel, this framework is optimized for building production-ready web applications.
While Next.js is based on React's component-based approach, it offers server-side rendering (SSR), static site generation (SSG), file-based routing, API routes, and much more. These features simplify the development process while increasing performance.
One of the most important features of Next.js is the "zero configuration" philosophy with its predefined configurations. This allows developers to start developing quickly without having to deal with complex webpack or babel configurations.
Creating a Project
Starting a new project with Next.js is quite easy. The following command will create a modern Next.js application:
npx create-next-app@latest my-nextjs-app
# or
yarn create next-app my-nextjs-app
# or
pnpm create next-app my-nextjs-appThis command will ask for preferences regarding TypeScript support, ESLint configuration, and other modern features. Default values will often be sufficient.
After the project is created, you can start the development server with the following command:
cd my-nextjs-app
npm run devYou can see your application by going to http://localhost:3000 in your browser.
File-Based Routing
One of the most powerful features of Next.js is its file system-based routing mechanism. This means you don't need to do any special router configuration to organize your application's pages and routes.
In Next.js 13 and later versions, the 'App Router' structure is used. In this structure, each folder within the
appdirectory corresponds to a route. Within each route folder, there is a <code>page.js</code> or <code>page.tsx</code> file that creates the content of the page:
app/ # Main app directory
├── page.tsx # Home page (/)
├── about/ # Folder for About page
│ └── page.tsx # About page (/about)
├── blog/ # Folder for Blog route
│ ├── page.tsx # Blog main page (/blog)
│ └── [slug]/ # Folder for dynamic route
│ └── page.tsx # Specific blog post (/blog/my-post)File names have special meanings:
page.tsx: Defines the UI component for a route
ayout.tsx: Defines the common layout for one or more routes
loading.tsx: Shows the loading status for a route
error.tsx: Shows the error status for a route
not-found.tsx: Shows the 404 page
Dynamic routes are created with folder names in square brackets. For example, <code>[slug]</code> defines a dynamic parameter and this parameter can be accessed from within the component.
A Simple Page Example
Creating a page in Next.js is very simple. Here's a simple home page example in <code>app/page.tsx</code>:
// app/page.tsx
export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<h1 className="text-4xl font-bold">
My First Page with Next.js
</h1>
<p className="mt-4 text-xl">
Congratulations on starting to use Next.js!
</p>
<div className="mt-8">
<a
href="https://nextjs.org/docs"
className="text-blue-500 hover:text-blue-700 transition-colors"
>
Documentation →
</a>
</div>
</main>
);
}In Next.js 13 and later versions, all components are Server Components by default. If you want a component to run on the client side, you must add the <code>'use client'</code> directive to the top of the file.
Data Fetching Methods
Data fetching in Next.js is an important issue that determines how your application will be created. Next.js offers three basic data fetching methods:
1. Data Fetching with Server Components: It is the default and recommended method.
// app/users/page.tsx
async function getUsers() {
const res = await fetch('https://api.example.com/users')
if (!res.ok) throw new Error('Failed to load users')
return res.json()
}
export default async function UsersPage() {
const users = await getUsers()
return (
<div>
<h1>Users</h1>
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
)
}2. Data Fetching with useEffect in Client Components: Can be used in components running on the client side.
3. Using SWR or React Query: More advanced libraries can be used for client-side data fetching, caching, and revalidation.
In the Next.js App Router, data fetching with Server Components is generally the preferred method because it:
Data fetching process happens on the server side, not on the client side
Reduces JavaScript bundle size
Does not send sensitive information such as API keys to the client side
Provides better SEO results
Creating an API Route
Next.js also allows you to create your own API endpoints. This allows you to manage your application's server-side logic within the same project.
With the App Router, API routes are defined with <code>route.js</code> or <code>route.ts</code> files under the <code>app/api</code> directory:
// app/api/users/route.ts
import { NextResponse } from 'next/server'
export async function GET() {
// Connecting to database, fetching data etc.
const users = [
{ id: 1, name: "Ali Yilmaz" },
{ id: 2, name: "Ayse Kaya" }
]
return NextResponse.json(users)
}
export async function POST(request: Request) {
try {
// receiving request body
const body = await request.json()
// actions required to create a new user
// for example, saving to database
return NextResponse.json(
{ message: "User created", user: body },
{ status: 201 }
)
} catch (error) {
return NextResponse.json(
{ message: "An error occurred" },
{ status: 500 }
)
}
}You can use these API routes from the client side as follows:
// To get data
const response = await fetch('/api/users')
const users = await response.json()
// To send data
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'New User' })
})
const result = await response.json()Next.js Component Structure
With Next.js 13, the concepts of Server and Client Components arrived. This distinction determines where components will be rendered and what features they will have.
Server Components (Default)
Rendered on the server and sent to the client as HTML
Reduces JavaScript load
Can directly access database, file system etc.
Cannot use React hooks such as <code>useState</code>, <code>useEffect</code>
Cannot access browser APIs
Client Components
Defined with the <code>'use client'</code> directive
Rendered on the client side (in the browser)
Can use interactive state and event management
Can use React hooks
Can access browser APIs
// Server Component example (app/users/page.tsx)
import UserProfile from './user-profile' // Client component
async function getUsers() {
// Database or API call
return [
{ id: 1, name: "Ali", email: "ali@example.com" },
{ id: 2, name: "Zeynep", email: "zeynep@example.com" }
]
}
export default async function UsersPage() {
const users = await getUsers()
return (
<div>
<h1 className="text-2xl font-bold mb-4">Users</h1>
{users.map(user => (
// Client component is being used within a Server Component
<UserProfile key={user.id} user={user} />
))}
</div>
)
}
// Client Component example (app/users/user-profile.tsx)
'use client'
import { useState } from 'react'
export default function UserProfile({ user }) {
const [expanded, setExpanded] = useState(false)
return (
<div className="border p-4 mb-2 rounded">
<h2 className="font-semibold">{user.name}</h2>
<button
onClick={() => setExpanded(!expanded)}
className="text-blue-500 mt-2"
>
{expanded ? 'Hide' : 'Show Details'}
</button>
{expanded && (
<div className="mt-2 text-gray-600">
<p>Email: {user.email}</p>
</div>
)}
</div>
)
}Image Optimization
Next.js provides image optimization for the modern web with the <code>Image</code> component:
Automatically optimizes images (formats like WebP, AVIF)
Makes images responsive
Applies lazy loading
Prevents Cumulative Layout Shift (CLS)
import Image from 'next/image' export default function ProductCard() { return ( <div className="relative w-full h-64"> <Image src="/images/product.jpg" // Image path alt="Product image" // Alt text for accessibility fill // Fills the main container sizes="(max-width: 768px) 100vw, 50vw" // Responsive size priority={false} // Lazy loading (default) className="object-cover rounded-lg" // Tailwind classes /> </div> ) }
Best Practices and Tips
Some best practices and tips to consider for efficiency and performance when developing with Next.js:
File and Folder Structure
Separate page and component logic with App Router (<code>app/</code> directory)
Keep general components in the <code>components/</code> folder
Collect helper functions under <code>lib/</code> or <code>utils/</code>
Performance Improvements
Prefer Server Components as much as possible
Use the <code>Image</code> component for image optimization
Render static content as statically as possible
Security Recommendations
Always validate input data in API routes
Store secret keys and credentials in environment variables
Add CSRF and XSS protections
These tips and best practices will help you have a smooth experience while developing with Next.js and create high-quality applications.
