Web Development

Modern Web Stack Guide: Next.js 15 + TypeScript

A comprehensive guide to building production-ready web applications with Next.js 15, TypeScript, and modern tooling.

MFS Yazılım Team

Full-Stack Developer

10/12/2025
10 min read
#Next.js
#TypeScript
#React
#Web Development
#Full Stack

Modern Web Stack Guide: Next.js 15 + TypeScript

Building modern web applications requires more than just knowing React. In this comprehensive guide, we'll explore how to leverage Next.js 15 with TypeScript to create production-ready applications.

Why Next.js 15?

Next.js 15 brings significant improvements that make it the go-to framework for React applications:

  • App Router: File-based routing with layouts, server components, and streaming
  • Server Components: Reduce JavaScript bundle size and improve performance
  • Server Actions: Type-safe mutations without API routes
  • Partial Pre-rendering: Combine static and dynamic content
  • Turbopack: Lightning-fast bundler for development

Project Setup

Initialize Your Project

npx create-next-app@latest my-app --typescript --app --tailwind cd my-app npm install

Essential Dependencies

# UI Components npm install @radix-ui/react-dialog @radix-ui/react-dropdown-menu npm install class-variance-authority clsx tailwind-merge # Forms & Validation npm install react-hook-form @hookform/resolvers zod # State Management (if needed) npm install zustand # Database (Prisma example) npm install @prisma/client npm install -D prisma

TypeScript Best Practices

Strict Type Safety

Enable strict mode in tsconfig.json:

{ "compilerOptions": { "strict": true, "noUncheckedIndexedAccess": true, "noImplicitOverride": true, "noUnusedLocals": true, "noUnusedParameters": true } }

Type-Safe API Routes

// app/api/users/route.ts import { NextRequest, NextResponse } from 'next/server'; import { z } from 'zod'; const userSchema = z.object({ name: z.string().min(2), email: z.string().email(), }); export async function POST(request: NextRequest) { try { const body = await request.json(); const { name, email } = userSchema.parse(body); // Your logic here return NextResponse.json({ success: true }); } catch (error) { if (error instanceof z.ZodError) { return NextResponse.json( { error: error.errors }, { status: 400 } ); } return NextResponse.json( { error: 'Internal server error' }, { status: 500 } ); } }

Server Components vs Client Components

When to Use Server Components

  • Fetching data from databases
  • Accessing backend resources
  • Keeping sensitive information server-side
  • Large dependencies that don't need client JavaScript
// app/products/page.tsx (Server Component) import { db } from '@/lib/db'; export default async function ProductsPage() { const products = await db.product.findMany(); return ( <div> {products.map(product => ( <ProductCard key={product.id} product={product} /> ))} </div> ); }

When to Use Client Components

  • Using React hooks (useState, useEffect, etc.)
  • Handling browser events
  • Using browser-only APIs
  • Interactive components
'use client'; import { useState } from 'react'; export function Counter() { const [count, setCount] = useState(0); return ( <button onClick={() => setCount(count + 1)}> Count: {count} </button> ); }

Data Fetching Patterns

Server-Side Data Fetching

// app/posts/[id]/page.tsx async function getPost(id: string) { const res = await fetch(`https://api.example.com/posts/${id}`, { next: { revalidate: 3600 }, // Revalidate every hour }); if (!res.ok) throw new Error('Failed to fetch post'); return res.json(); } export default async function PostPage({ params, }: { params: Promise<{ id: string }>; }) { const { id } = await params; const post = await getPost(id); return <article>{/* Render post */}</article>; }

Parallel Data Fetching

async function getData() { const [users, posts, comments] = await Promise.all([ fetch('/api/users').then(r => r.json()), fetch('/api/posts').then(r => r.json()), fetch('/api/comments').then(r => r.json()), ]); return { users, posts, comments }; }

Form Handling with React Hook Form + Zod

'use client'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; const formSchema = z.object({ email: z.string().email('Invalid email address'), password: z.string().min(8, 'Password must be at least 8 characters'), }); type FormData = z.infer<typeof formSchema>; export function LoginForm() { const { register, handleSubmit, formState: { errors, isSubmitting }, } = useForm<FormData>({ resolver: zodResolver(formSchema), }); const onSubmit = async (data: FormData) => { const response = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); if (response.ok) { // Handle success } }; return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register('email')} type="email" /> {errors.email && <span>{errors.email.message}</span>} <input {...register('password')} type="password" /> {errors.password && <span>{errors.password.message}</span>} <button type="submit" disabled={isSubmitting}> {isSubmitting ? 'Loading...' : 'Login'} </button> </form> ); }

Database Integration with Prisma

Schema Definition

// prisma/schema.prisma generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model User { id String @id @default(cuid()) email String @unique name String? posts Post[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Post { id String @id @default(cuid()) title String content String? published Boolean @default(false) author User @relation(fields: [authorId], references: [id]) authorId String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt }

Database Client

// lib/db.ts import { PrismaClient } from '@prisma/client'; const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined; }; export const db = globalForPrisma.prisma ?? new PrismaClient(); if (process.env.NODE_ENV !== 'production') { globalForPrisma.prisma = db; }

Performance Optimization

Image Optimization

import Image from 'next/image'; export function ProductImage({ src, alt }: { src: string; alt: string }) { return ( <Image src={src} alt={alt} width={800} height={600} quality={85} placeholder="blur" blurDataURL="/placeholder.jpg" loading="lazy" /> ); }

Code Splitting

import dynamic from 'next/dynamic'; const HeavyComponent = dynamic(() => import('@/components/HeavyComponent'), { loading: () => <p>Loading...</p>, ssr: false, // Disable server-side rendering if not needed });

Testing Strategy

Unit Tests with Jest

// __tests__/utils.test.ts import { formatCurrency } from '@/lib/utils'; describe('formatCurrency', () => { it('formats USD correctly', () => { expect(formatCurrency(1234.56, 'USD')).toBe('$1,234.56'); }); it('handles zero correctly', () => { expect(formatCurrency(0, 'USD')).toBe('$0.00'); }); });

Integration Tests with Playwright

// e2e/login.spec.ts import { test, expect } from '@playwright/test'; test('user can login', async ({ page }) => { await page.goto('/login'); await page.fill('[name="email"]', 'user@example.com'); await page.fill('[name="password"]', 'password123'); await page.click('button[type="submit"]'); await expect(page).toHaveURL('/dashboard'); });

Deployment Checklist

  • ✅ Environment variables configured
  • ✅ Database migrations run
  • ✅ Build passes without errors
  • ✅ TypeScript types validate
  • ✅ Tests pass
  • ✅ Performance metrics meet targets (Lighthouse score > 90)
  • ✅ SEO metadata configured
  • ✅ Analytics integrated
  • ✅ Error tracking setup (Sentry, etc.)

Conclusion

Next.js 15 with TypeScript provides a robust foundation for building modern web applications. By following these patterns and best practices, you'll create maintainable, performant applications that scale.

Ready to build your next project? Get in touch and let's discuss your requirements.

Ready to Transform Your Business?

Let's discuss how we can help you implement these solutions in your organization.

Found this article helpful? Share it on social media!

Modern Web Stack Guide: Next.js 15 + TypeScript | MFS Yazılım Blog