Falytom logoFALYTOM
Games
ToolsUI ComponentsServicesProductsAbout
menu
  1. Home
  2. /Web Development
  3. /Nextjs
  4. /Form Validation
Your Progress0%
0 of 70 completed

NextJS Topics

Getting Started

  • What is Next.js?
  • Next.js vs React - Key Differences
  • Creating Your First Next.js 15 Project
  • Next.js Project Structure Explained
  • App Router vs Pages Router

Routing Fundamentals

  • File-Based Routing Basics
  • Creating Pages with page.tsx
  • Dynamic Routes and Route Parameters
  • Catch-All and Optional Catch-All Routes
  • Route Groups for Organization
  • Parallel Routes
  • Intercepting Routes

Layouts and Pages

  • Understanding Layouts
  • Root Layout and Global Configuration
  • Nested Layouts
  • Templates vs Layouts
  • Loading States with loading.tsx
  • Error Handling with error.tsx

Server and Client Components

  • Understanding Server Components
  • Client Components with 'use client'
  • When to Use Server vs Client Components
  • Component Composition Patterns
  • Passing Props Between Server and Client
  • Server Component Patterns and Best Practices

Data Fetching

  • Fetching Data in Server Components
  • Parallel and Sequential Data Fetching
  • Data Fetching Patterns and Strategies
  • Caching and Revalidation
  • Revalidate and Cache Tags
  • Handling Loading and Error States in Data Fetching

Navigation and Links

  • Link Component Basics
  • useRouter Hook for Programmatic Navigation
  • usePathname and useSearchParams Hooks
  • Active Links and Navigation States
  • Redirects and Navigation Guards

Styling in Next.js

  • CSS Modules in Next.js
  • Tailwind CSS Setup and Configuration
  • Global Styles and CSS Variables
  • Next.js Font Optimization

Images and Media

  • Next.js Image Component Basics
  • Image Optimization and Best Practices
  • Working with Static Assets

Forms and Data Mutations

  • Understanding Server Actions
  • Form Handling with Server Actions
  • Form Validation and Error Handling
  • useFormStatus and useFormState Hooks
  • Revalidating Data After Mutations
  • Optimistic Updates

Metadata and SEO

  • Static Metadata Configuration
  • Dynamic Metadata Generation
  • Open Graph and Social Media Cards
  • Sitemap and Robots.txt

API Routes and Route Handlers

  • Route Handlers Introduction
  • GET and POST Request Handlers
  • Dynamic API Routes
  • Request and Response Objects
  • API Error Handling and Status Codes

Middleware and Advanced Features

  • Introduction to Middleware
  • Authentication with Middleware
  • Environment Variables and Configuration
  • Streaming and Suspense
  • Not Found and Global Error Pages

Deployment and Optimization

  • Understanding Static and Dynamic Rendering
  • generateStaticParams for Static Generation
  • Build and Production Optimization
  • Deploying to Vercel
  • Alternative Deployment Options
  • Performance Monitoring and Analytics

Real-World Projects

  • Project 1: E-commerce Product Catalog
  • Project 2: Dashboard with Authentication

Form Validation and Error Handling

Robust validation with Zod and Server Actions

Good form validation protects your data and guides users to success. Zod provides type-safe schema validation that works seamlessly with Server Actions, giving you runtime validation and TypeScript types from a single schema. With proper error handling, you can display field-specific errors, validate complex rules, and create forms that help users fix mistakes easily. Let's master form validation!

Why Zod for Validation?

❌ Manual Validation

TYPESCRIPT
export async function createPost(formData: FormData) {
  const title = formData.get('title') as string;
  const email = formData.get('email') as string;
  
  // Manual validation
  if (!title) {
    return { error: 'Title required' };
  }
  if (title.length < 3) {
    return { error: 'Title too short' };
  }
  if (!email.includes('@')) {
    return { error: 'Invalid email' };
  }
  
  // Problems:
  // ❌ No type safety
  // ❌ Verbose and error-prone
  // ❌ Hard to maintain
  // ❌ No TypeScript inference
  // ❌ Easy to miss validations
}

✅ Zod Validation

TYPESCRIPT
import { z } from 'zod';

const schema = z.object({
  title: z.string().min(3),
  email: z.string().email(),
});

export async function createPost(formData: FormData) {
  const result = schema.safeParse({
    title: formData.get('title'),
    email: formData.get('email'),
  });
  
  if (!result.success) {
    return { errors: result.error.flatten() };
  }
  
  // result.data is typed!
  const { title, email } = result.data;
  
  // Benefits:
  // ✅ Type-safe
  // ✅ Concise
  // ✅ Maintainable
  // ✅ TypeScript types inferred
  // ✅ Rich validation rules
}

Key Benefits of Zod

  • Type safety: Infer TypeScript types from schemas
  • Runtime validation: Validate data at runtime
  • Rich validation: Email, URL, min/max, regex, custom rules
  • Error messages: Detailed, customizable error messages
  • Composable: Reuse and combine schemas
  • Zero dependencies: Lightweight and fast

Installing Zod

BASH
npm install zod

# Or with other package managers:
yarn add zod
pnpm add zod

Basic Zod Validation

Creating a Schema

app/schemas/post.ts
import { z } from 'zod';

export const createPostSchema = z.object({
  title: z.string().min(3, 'Title must be at least 3 characters'),
  slug: z.string().min(1, 'Slug is required'),
  content: z.string().min(10, 'Content must be at least 10 characters'),
  excerpt: z.string().optional(),
  category: z.enum(['tech', 'design', 'business'], {
    errorMap: () => ({ message: 'Invalid category' }),
  }),
  published: z.boolean().default(false),
});

// Infer TypeScript type from schema
export type CreatePostInput = z.infer<typeof createPostSchema>;

// ✅ Schema defines all validation rules
// ✅ Custom error messages
// ✅ TypeScript type inferred automatically

Using Schema in Server Action

app/actions/posts.ts
'use server';

import { createPostSchema } from '@/app/schemas/post';
import { revalidatePath } from 'next/cache';

export async function createPost(formData: FormData) {
  // Parse FormData into object
  const data = {
    title: formData.get('title'),
    slug: formData.get('slug'),
    content: formData.get('content'),
    excerpt: formData.get('excerpt'),
    category: formData.get('category'),
    published: formData.get('published') === 'on',
  };

  // Validate with Zod
  const result = createPostSchema.safeParse(data);

  if (!result.success) {
    // Return validation errors
    return {
      success: false,
      errors: result.error.flatten().fieldErrors,
    };
  }

  // Type-safe validated data
  const validatedData = result.data;

  // Save to database
  await db.posts.create({
    data: validatedData,
  });

  revalidatePath('/blog');

  return { success: true };
}

// ✅ safeParse returns { success, data?, error? }
// ✅ flatten() gives field-specific errors
// ✅ result.data is type-safe

Displaying Errors in Form

app/create-post/page.tsx
'use client';

import { createPost } from '@/app/actions/posts';
import { useState } from 'react';

export default function CreatePostPage() {
  const [errors, setErrors] = useState<Record<string, string[]>>({});

  async function handleSubmit(formData: FormData) {
    const result = await createPost(formData);
    
    if (!result.success) {
      setErrors(result.errors || {});
    } else {
      // Success - clear errors
      setErrors({});
      alert('Post created!');
    }
  }

  return (
    <form action={handleSubmit} className="space-y-6">
      {/* Title field */}
      <div>
        <label htmlFor="title" className="block font-semibold mb-2">
          Title
        </label>
        <input
          type="text"
          id="title"
          name="title"
          className={`w-full px-4 py-2 border rounded-lg ${
            errors.title ? 'border-red-500' : 'border-gray-300'
          }`}
        />
        {errors.title && (
          <p className="text-red-600 text-sm mt-1">{errors.title[0]}</p>
        )}
      </div>

      {/* Content field */}
      <div>
        <label htmlFor="content" className="block font-semibold mb-2">
          Content
        </label>
        <textarea
          id="content"
          name="content"
          rows={10}
          className={`w-full px-4 py-2 border rounded-lg ${
            errors.content ? 'border-red-500' : 'border-gray-300'
          }`}
        />
        {errors.content && (
          <p className="text-red-600 text-sm mt-1">{errors.content[0]}</p>
        )}
      </div>

      {/* Category field */}
      <div>
        <label htmlFor="category" className="block font-semibold mb-2">
          Category
        </label>
        <select
          id="category"
          name="category"
          className={`w-full px-4 py-2 border rounded-lg ${
            errors.category ? 'border-red-500' : 'border-gray-300'
          }`}
        >
          <option value="">Select category</option>
          <option value="tech">Technology</option>
          <option value="design">Design</option>
          <option value="business">Business</option>
        </select>
        {errors.category && (
          <p className="text-red-600 text-sm mt-1">{errors.category[0]}</p>
        )}
      </div>

      <button
        type="submit"
        className="w-full px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
      >
        Create Post
      </button>
    </form>
  );
}

// ✅ Field-specific error display
// ✅ Red border on error
// ✅ Error message below field
// ✅ Clear on success

Common Validation Rules

String Validations

TYPESCRIPT
import { z } from 'zod';

const schema = z.object({
  // Required string
  name: z.string().min(1, 'Name is required'),
  
  // Min/max length
  username: z.string().min(3).max(20),
  
  // Email validation
  email: z.string().email('Invalid email address'),
  
  // URL validation
  website: z.string().url('Invalid URL'),
  
  // Regex validation
  phone: z.string().regex(/^d{10}$/, 'Phone must be 10 digits'),
  
  // Starts/ends with
  slug: z.string().regex(/^[a-z0-9-]+$/, 'Slug can only contain lowercase, numbers, and hyphens'),
  
  // Optional string
  bio: z.string().optional(),
  
  // String with default
  role: z.string().default('user'),
});

// ✅ Rich string validation
// ✅ Custom error messages
// ✅ Optional and default values

Number Validations

TYPESCRIPT
const schema = z.object({
  // Number with min/max
  age: z.number().min(18, 'Must be 18 or older').max(120),
  
  // Positive/negative
  price: z.number().positive('Price must be positive'),
  rating: z.number().min(-5).max(5),
  
  // Integer only
  quantity: z.number().int('Must be a whole number'),
  
  // Parse string to number
  port: z.coerce.number().int().positive(),
  
  // Optional number
  discount: z.number().optional(),
});

// When parsing FormData (strings):
const data = {
  age: parseInt(formData.get('age') as string, 10),
  price: parseFloat(formData.get('price') as string),
};

// Or use coerce to auto-convert:
const schema = z.object({
  age: z.coerce.number().min(18),
  price: z.coerce.number().positive(),
});

// ✅ Number validation
// ✅ Coercion from strings
// ✅ Min/max ranges

Date Validations

TYPESCRIPT
const schema = z.object({
  // Date validation
  birthDate: z.date(),
  
  // Date in the past
  eventDate: z.date().min(new Date(), 'Date must be in the future'),
  
  // Date range
  appointmentDate: z.date()
    .min(new Date())
    .max(new Date('2025-12-31')),
  
  // Parse string to date
  scheduledAt: z.coerce.date(),
});

// When parsing FormData:
const data = {
  eventDate: new Date(formData.get('eventDate') as string),
};

// Or use coerce:
const schema = z.object({
  eventDate: z.coerce.date().min(new Date()),
});

// ✅ Date validation
// ✅ Min/max dates
// ✅ Coercion from strings

Boolean and Enum Validations

TYPESCRIPT
const schema = z.object({
  // Boolean
  published: z.boolean(),
  
  // Enum (specific values)
  status: z.enum(['draft', 'published', 'archived']),
  
  // Native enum
  role: z.nativeEnum(UserRole),
  
  // Literal values
  type: z.literal('admin'),
  
  // Union of literals
  priority: z.union([
    z.literal('low'),
    z.literal('medium'),
    z.literal('high'),
  ]),
});

// For checkboxes in FormData:
const data = {
  published: formData.get('published') === 'on',
};

// ✅ Boolean from checkbox
// ✅ Enum validation
// ✅ Specific allowed values

Array Validations

TYPESCRIPT
const schema = z.object({
  // Array of strings
  tags: z.array(z.string()).min(1, 'At least one tag required'),
  
  // Array with max length
  categories: z.array(z.string()).max(5, 'Maximum 5 categories'),
  
  // Array of objects
  items: z.array(z.object({
    name: z.string(),
    quantity: z.number().positive(),
  })),
  
  // Optional array
  hobbies: z.array(z.string()).optional(),
});

// For multiple checkboxes in FormData:
const data = {
  tags: formData.getAll('tags') as string[],
};

// ✅ Array validation
// ✅ Min/max length
// ✅ Complex nested validation

Object and Nested Validations

TYPESCRIPT
const schema = z.object({
  // Nested object
  address: z.object({
    street: z.string(),
    city: z.string(),
    zipCode: z.string().regex(/^d{5}$/),
  }),
  
  // Optional nested object
  billing: z.object({
    cardNumber: z.string(),
    cvv: z.string(),
  }).optional(),
  
  // Array of objects
  contacts: z.array(z.object({
    name: z.string(),
    email: z.string().email(),
  })),
});

// ✅ Nested validation
// ✅ Complex structures
// ✅ Reusable schemas

Custom Validation Rules

Refinements (Custom Logic)

TYPESCRIPT
const schema = z.object({
  password: z.string().min(8),
  confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
  message: 'Passwords do not match',
  path: ['confirmPassword'], // Which field to show error on
});

// Multiple refinements:
const schema = z.object({
  startDate: z.date(),
  endDate: z.date(),
})
  .refine((data) => data.endDate > data.startDate, {
    message: 'End date must be after start date',
    path: ['endDate'],
  })
  .refine((data) => {
    const days = (data.endDate.getTime() - data.startDate.getTime()) / (1000 * 60 * 60 * 24);
    return days <= 365;
  }, {
    message: 'Date range cannot exceed 1 year',
    path: ['endDate'],
  });

// ✅ Custom validation logic
// ✅ Cross-field validation
// ✅ Specify error path

Transform Data

TYPESCRIPT
const schema = z.object({
  // Trim whitespace
  email: z.string().email().transform(val => val.toLowerCase().trim()),
  
  // Convert to number
  age: z.string().transform(val => parseInt(val, 10)),
  
  // Parse JSON
  metadata: z.string().transform(val => JSON.parse(val)),
  
  // Convert slug
  slug: z.string().transform(val => 
    val.toLowerCase().replace(/s+/g, '-')
  ),
});

// ✅ Transform after validation
// ✅ Normalize data
// ✅ Type-safe transformations

Async Validation

TYPESCRIPT
import { z } from 'zod';

const schema = z.object({
  email: z.string().email(),
  username: z.string().min(3),
}).refine(
  async (data) => {
    // Check if email already exists
    const existingUser = await db.users.findUnique({
      where: { email: data.email },
    });
    return !existingUser;
  },
  {
    message: 'Email already registered',
    path: ['email'],
  }
);

// Use parseAsync instead of parse:
export async function register(formData: FormData) {
  const result = await schema.safeParseAsync({
    email: formData.get('email'),
    username: formData.get('username'),
  });
  
  if (!result.success) {
    return { errors: result.error.flatten().fieldErrors };
  }
  
  // Continue...
}

// ✅ Async database checks
// ✅ API validations
// ✅ Use safeParseAsync

Complete Validation Example

Registration Form with Full Validation

app/schemas/user.ts
import { z } from 'zod';

export const registerSchema = z.object({
  name: z.string()
    .min(2, 'Name must be at least 2 characters')
    .max(50, 'Name must be less than 50 characters'),
  
  email: z.string()
    .email('Invalid email address')
    .transform(val => val.toLowerCase().trim()),
  
  password: z.string()
    .min(8, 'Password must be at least 8 characters')
    .regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
    .regex(/[a-z]/, 'Password must contain at least one lowercase letter')
    .regex(/[0-9]/, 'Password must contain at least one number'),
  
  confirmPassword: z.string(),
  
  age: z.coerce.number()
    .int('Age must be a whole number')
    .min(18, 'You must be at least 18 years old')
    .max(120, 'Invalid age'),
  
  agreeToTerms: z.literal(true, {
    errorMap: () => ({ message: 'You must agree to the terms' }),
  }),
  
  newsletter: z.boolean().optional(),
})
  .refine((data) => data.password === data.confirmPassword, {
    message: 'Passwords do not match',
    path: ['confirmPassword'],
  });

export type RegisterInput = z.infer<typeof registerSchema>;

// ✅ Complete validation schema
// ✅ Password requirements
// ✅ Cross-field validation
// ✅ Type inference
app/actions/auth.ts
'use server';

import { registerSchema } from '@/app/schemas/user';
import { hash } from 'bcrypt';
import { redirect } from 'next/navigation';

export async function register(formData: FormData) {
  // Parse FormData
  const data = {
    name: formData.get('name'),
    email: formData.get('email'),
    password: formData.get('password'),
    confirmPassword: formData.get('confirmPassword'),
    age: formData.get('age'),
    agreeToTerms: formData.get('agreeToTerms') === 'on',
    newsletter: formData.get('newsletter') === 'on',
  };

  // Validate
  const result = registerSchema.safeParse(data);

  if (!result.success) {
    return {
      success: false,
      errors: result.error.flatten().fieldErrors,
    };
  }

  const validatedData = result.data;

  // Check if user exists
  const existingUser = await db.users.findUnique({
    where: { email: validatedData.email },
  });

  if (existingUser) {
    return {
      success: false,
      errors: {
        email: ['Email already registered'],
      },
    };
  }

  // Hash password
  const hashedPassword = await hash(validatedData.password, 10);

  // Create user
  await db.users.create({
    data: {
      name: validatedData.name,
      email: validatedData.email,
      password: hashedPassword,
      age: validatedData.age,
      newsletter: validatedData.newsletter || false,
    },
  });

  // Redirect to login
  redirect('/login');
}

// ✅ Zod validation
// ✅ Database checks
// ✅ Password hashing
// ✅ Redirect on success
app/components/FormField.tsx
interface FormFieldProps {
  label: string;
  name: string;
  type?: string;
  required?: boolean;
  error?: string[];
  className?: string;
}

export function FormField({
  label,
  name,
  type = 'text',
  required = false,
  error,
  className = '',
}: FormFieldProps) {
  const hasError = error && error.length > 0;

  return (
    <div className={className}>
      <label htmlFor={name} className="block font-semibold mb-2">
        {label}
        {required && <span className="text-red-600 ml-1">*</span>}
      </label>
      
      <input
        type={type}
        id={name}
        name={name}
        required={required}
        aria-invalid={hasError}
        aria-describedby={hasError ? `${name}-error` : undefined}
        className={`w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 ${
          hasError
            ? 'border-red-500 focus:ring-red-500'
            : 'border-gray-300'
        }`}
      />
      
      {hasError && (
        <p
          id={`${name}-error`}
          className="text-red-600 text-sm mt-1"
          role="alert"
        >
          {error[0]}
        </p>
      )}
    </div>
  );
}

// ✅ Reusable form field
// ✅ Accessible error display
// ✅ Visual error states
// ✅ ARIA attributes
app/register/page.tsx
'use client';

import { register } from '@/app/actions/auth';
import { FormField } from '@/app/components/FormField';
import { useState } from 'react';

export default function RegisterPage() {
  const [errors, setErrors] = useState<Record<string, string[]>>({});

  async function handleSubmit(formData: FormData) {
    const result = await register(formData);
    
    if (!result.success) {
      setErrors(result.errors || {});
    }
  }

  return (
    <div className="max-w-md mx-auto p-8">
      <h1 className="text-3xl font-bold mb-8">Create Account</h1>

      <form action={handleSubmit} className="space-y-6">
        <FormField
          label="Full Name"
          name="name"
          required
          error={errors.name}
        />

        <FormField
          label="Email"
          name="email"
          type="email"
          required
          error={errors.email}
        />

        <FormField
          label="Password"
          name="password"
          type="password"
          required
          error={errors.password}
        />

        <FormField
          label="Confirm Password"
          name="confirmPassword"
          type="password"
          required
          error={errors.confirmPassword}
        />

        <FormField
          label="Age"
          name="age"
          type="number"
          required
          error={errors.age}
        />

        <div>
          <label className="flex items-start gap-2">
            <input
              type="checkbox"
              name="agreeToTerms"
              required
              className="mt-1"
            />
            <span className="text-sm">
              I agree to the{' '}
              <a href="/terms" className="text-blue-600 hover:underline">
                Terms of Service
              </a>
            </span>
          </label>
          {errors.agreeToTerms && (
            <p className="text-red-600 text-sm mt-1">
              {errors.agreeToTerms[0]}
            </p>
          )}
        </div>

        <div>
          <label className="flex items-center gap-2">
            <input type="checkbox" name="newsletter" />
            <span className="text-sm">Subscribe to newsletter</span>
          </label>
        </div>

        <button
          type="submit"
          className="w-full px-6 py-3 bg-blue-600 text-white rounded-lg font-semibold hover:bg-blue-700"
        >
          Create Account
        </button>
      </form>
    </div>
  );
}

// ✅ Complete registration form
// ✅ Reusable FormField components
// ✅ All validation rules applied
// ✅ Accessible and user-friendly

Validation Project Structure

Organization of schemas and validated forms

appImportant

Select a file or folder to see details

Form Validation Best Practices

1. Always Validate on Server

TYPESCRIPT
// ✅ GOOD: Server-side validation (required)
export async function createPost(formData: FormData) {
  const result = schema.safeParse(data);
  if (!result.success) {
    return { errors: result.error.flatten() };
  }
  // Process validated data
}

// ❌ BAD: Client-only validation
// Never trust client-side validation alone
// Users can bypass it with dev tools

2. Provide Clear Error Messages

TYPESCRIPT
// ✅ GOOD: Clear, actionable errors
z.string().min(8, 'Password must be at least 8 characters')
z.string().email('Please enter a valid email address')
z.number().positive('Price must be greater than 0')

// ❌ BAD: Generic errors
z.string().min(8) // "String must contain at least 8 character(s)"
z.string().email() // "Invalid email"

// Users need to know how to fix errors

3. Use Field-Specific Errors

TYPESCRIPT
// ✅ GOOD: Field-specific errors
return {
  errors: {
    email: ['Email already registered'],
    password: ['Password too weak'],
  }
};

// Display next to each field
{errors.email && <p className="text-red-600">{errors.email[0]}</p>}

// ❌ BAD: Generic error
return { error: 'Form validation failed' };
// User doesn't know which fields are wrong

4. Validate Early, Return Fast

TYPESCRIPT
// ✅ GOOD: Validate before expensive operations
export async function createPost(formData: FormData) {
  // Validate first
  const result = schema.safeParse(data);
  if (!result.success) {
    return { errors: result.error.flatten() };
  }
  
  // Then do expensive operations
  await uploadImage();
  await saveToDatabase();
}

// Don't process invalid data

5. Reuse Schemas

TYPESCRIPT
// ✅ GOOD: Shared schemas
// schemas/user.ts
export const userEmailSchema = z.string().email();
export const userPasswordSchema = z.string().min(8);

export const loginSchema = z.object({
  email: userEmailSchema,
  password: userPasswordSchema,
});

export const registerSchema = z.object({
  name: z.string().min(2),
  email: userEmailSchema,
  password: userPasswordSchema,
  confirmPassword: userPasswordSchema,
});

// Consistent validation across forms

Key Takeaways

  • Zod for validation - type-safe schema validation
  • safeParse() - returns result without throwing
  • Server-side required - always validate on server
  • Field-specific errors - flatten().fieldErrors
  • Custom validation - refine() for complex rules
  • Clear error messages - help users fix mistakes
  • Reusable schemas - DRY validation logic

What's Next?

You've mastered form validation with Zod! Next, we'll explore useFormStatus and useFormState Hooks—managing form loading states, pending submissions, and building forms with instant feedback using React's form hooks. You'll create forms that feel responsive and provide great UX!

We'll cover useFormStatus for submit button states, useFormState for form-level state management, and combining both for complete form experiences.

🛡️ Security First

Never trust client-side validation. Always validate on the server. Client-side validation is only for UX—anyone can bypass it. Server-side validation protects your data and application.

Test Your Understanding

Question 1 of 4

Why use Zod for form validation?

Master form validation with Zod in Next.js! Learn server-side validation, error handling, and creating user-friendly forms.

Previous
Form Handling with Server Actions
Next
useFormStatus and useFormState Hooks

Master Next.js Forms

Join 2,000+ developers building robust Next.js forms. Get the next lesson on form status and state hooks - 100% FREE.

You might also like

UI Components

Ready-made components for your projects

Developer Tools

Boost productivity with handy generators

HTML Game

Learn HTML concepts through interactive gameplay

Join Our Web Development Training Program

Master frontend & backend development with hands-on projects.

NextJS Tutorials

0 of 70 completed

Your Progress0%

Topics

Getting Started

  • What is Next.js?
  • Next.js vs React - Key Differences
  • Creating Your First Next.js 15 Project
  • Next.js Project Structure Explained
  • App Router vs Pages Router

Routing Fundamentals

  • File-Based Routing Basics
  • Creating Pages with page.tsx
  • Dynamic Routes and Route Parameters
  • Catch-All and Optional Catch-All Routes
  • Route Groups for Organization
  • Parallel Routes
  • Intercepting Routes

Layouts and Pages

  • Understanding Layouts
  • Root Layout and Global Configuration
  • Nested Layouts
  • Templates vs Layouts
  • Loading States with loading.tsx
  • Error Handling with error.tsx

Server and Client Components

  • Understanding Server Components
  • Client Components with 'use client'
  • When to Use Server vs Client Components
  • Component Composition Patterns
  • Passing Props Between Server and Client
  • Server Component Patterns and Best Practices

Data Fetching

  • Fetching Data in Server Components
  • Parallel and Sequential Data Fetching
  • Data Fetching Patterns and Strategies
  • Caching and Revalidation
  • Revalidate and Cache Tags
  • Handling Loading and Error States in Data Fetching

Navigation and Links

  • Link Component Basics
  • useRouter Hook for Programmatic Navigation
  • usePathname and useSearchParams Hooks
  • Active Links and Navigation States
  • Redirects and Navigation Guards

Styling in Next.js

  • CSS Modules in Next.js
  • Tailwind CSS Setup and Configuration
  • Global Styles and CSS Variables
  • Next.js Font Optimization

Images and Media

  • Next.js Image Component Basics
  • Image Optimization and Best Practices
  • Working with Static Assets

Forms and Data Mutations

  • Understanding Server Actions
  • Form Handling with Server Actions
  • Form Validation and Error Handling
  • useFormStatus and useFormState Hooks
  • Revalidating Data After Mutations
  • Optimistic Updates

Metadata and SEO

  • Static Metadata Configuration
  • Dynamic Metadata Generation
  • Open Graph and Social Media Cards
  • Sitemap and Robots.txt

API Routes and Route Handlers

  • Route Handlers Introduction
  • GET and POST Request Handlers
  • Dynamic API Routes
  • Request and Response Objects
  • API Error Handling and Status Codes

Middleware and Advanced Features

  • Introduction to Middleware
  • Authentication with Middleware
  • Environment Variables and Configuration
  • Streaming and Suspense
  • Not Found and Global Error Pages

Deployment and Optimization

  • Understanding Static and Dynamic Rendering
  • generateStaticParams for Static Generation
  • Build and Production Optimization
  • Deploying to Vercel
  • Alternative Deployment Options
  • Performance Monitoring and Analytics

Real-World Projects

  • Project 1: E-commerce Product Catalog
  • Project 2: Dashboard with Authentication
Falytom logoFALYTOM

Learn technologies like HTML, CSS, JavaScript, React, NodeJS and more through interactive games, tutorials, components and free tools. Transform your businesses with AI chatbots, SEO-optimized websites, and professional development services.

© 2025 Tomilola Group
  • Facebook logo
  • Twitter logo
  • Instagram logo
  • LinkedIn logo
  • GitHub logo
  • YouTube logo