Back to Blog
React
6 min read
January 5, 2024

Modern Full-Stack Development with Next.js

Exploring the latest features and best practices for building modern web applications.

nextjsreactfullstacktypescriptweb-development


The Evolution of Next.js

Next.js has revolutionized how we build React applications. From its humble beginnings as a simple server-side rendering framework to the comprehensive full-stack platform it is today, it continues to push the boundaries of what's possible on the web.

Key Features in Modern Next.js

1. App Router

The new App Router introduces a powerful paradigm for building applications:

``typescript
// app/page.tsx
export default function HomePage() {
return (


Welcome to Modern Next.js



)
}

// app/about/page.tsx
export default function AboutPage() {
return

About us

}
`

2. Server Components

Server Components by default for improved performance:

`typescript
// Server Component (default)
async function UserProfile({ userId }: { userId: string }) {
const user = await db.user.findUnique({ where: { id: userId }})

return (


{user.name}


{user.email}



)
}

// Client Component with 'use client'
'use client'

import { useState } from 'react'

export function InteractiveButton() {
const [count, setCount] = useState(0)

return (

)
}
`

3. Data Fetching

Simplified data fetching with async components:

`typescript
async function BlogPost({ slug }: { slug: string }) {
const post = await fetch(
https://api.example.com/posts/${slug})
.then(res => res.json())

return (


{post.title}


{post.content}



)
}
`

4. Streaming

Progressive rendering with streaming:

`typescript
import { Suspense } from 'react'

async function BlogList() {
const posts = await getPosts()
return posts.map(post => )
}

export default function BlogPage() {
return (


}>



)
}
`

Best Practices

1. Component Organization

`typescript
// app/components/ui/button.tsx
export function Button({ children, ...props }: ButtonProps) {
return
}

// app/components/features/auth/login-form.tsx
export function LoginForm() {
return (




)
}
`

2. Type Safety

Leverage TypeScript throughout:

`typescript
interface User {
id: string
name: string
email: string
}

async function getUser(id: string): Promise {
const response = await fetch(
/api/users/${id})
return response.json()
}
`

3. API Routes

Build API endpoints with Route Handlers:

`typescript
// app/api/users/route.ts
import { NextResponse } from 'next/server'

export async function GET(request: Request) {
const users = await db.user.findMany()
return NextResponse.json(users)
}

export async function POST(request: Request) {
const body = await request.json()
const user = await db.user.create({ data: body })
return NextResponse.json(user, { status: 201 })
}
`

Performance Optimization

1. Image Optimization

`typescript
import Image from 'next/image'

function ProfilePicture() {
return (
src="/profile.jpg"
alt="Profile"
width={200}
height={200}
priority
/>
)
}
`

2. Code Splitting

Automatic code splitting with dynamic imports:

`typescript
import dynamic from 'next/dynamic'

const DynamicComponent = dynamic(() => import('./HeavyComponent'), {
loading: () => ,
ssr: false
})
`

3. Caching

Leverage Next.js caching:

`typescript
export const revalidate = 3600 // Revalidate every hour

async function Products() {
const products = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 }
}).then(res => res.json())

return
}
`

State Management

1. Server State

Use React Query or SWR for server state:

`typescript
'use client'

import { useQuery } from '@tanstack/react-query'

function UserProfile({ userId }: { userId: string }) {
const { data: user, isLoading } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetch(
/api/users/${userId}).then(r => r.json())
})

if (isLoading) return
return

{user.name}

}
`

2. Client State

Use Zustand or Context API for client state:

`typescript
import { create } from 'zustand'

const useStore = create((set) => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 }))
}))
`

Styling

1. Tailwind CSS

Modern utility-first CSS:

`typescript
export function Button() {
return (

)
}
`

2. CSS Modules

Component-scoped styles:

`typescript
import styles from './button.module.css'

export function Button() {
return
}
`

Testing

1. Component Testing

`typescript
import { render, screen } from '@testing-library/react'
import { Button } from './button'

test('renders button', () => {
render()
expect(screen.getByText('Click me')).toBeInTheDocument()
})
`

2. E2E Testing

`typescript
import { test, expect } from '@playwright/test'

test('user can login', async ({ page }) => {
await page.goto('/login')
await page.fill('input[name="email"]', 'user@example.com')
await page.fill('input[name="password"]', 'password')
await page.click('button[type="submit"]')

await expect(page).toHaveURL('/dashboard')
})
`

Deployment

Vercel Integration

Seamless deployment with Vercel:

`bash

Install Vercel CLI


npm i -g vercel

Deploy


vercel
`

Environment Variables

`env

.env.local


DATABASE_URL=postgresql://...
NEXT_PUBLIC_API_URL=https://api.example.com
``

Conclusion

Next.js provides a comprehensive platform for building modern web applications. By leveraging its features and following best practices, you can build fast, scalable, and maintainable applications.

The key is to understand the tooling, choose the right patterns for your use case, and continuously iterate based on your needs.