Falytom logoFALYTOM
Games
ToolsUI ComponentsServicesProductsAbout
menu
  1. Home
  2. /Web Development
  3. /Nextjs
  4. /Forms Server Actions
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 Handling with Server Actions

Building accessible, progressively enhanced forms

Forms are fundamental to web applications. With Server Actions, you can build progressively enhanced forms that work without JavaScript, provide excellent user experience, and handle complex scenarios like file uploads and multi-step flows. Server Actions eliminate the need for API routes, giving you type-safe, co-located form handling. Let's build production-ready forms!

Basic Form with Server Action

Simple Contact Form

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

import { revalidatePath } from 'next/cache';

export async function submitContact(formData: FormData) {
  // Extract form fields
  const name = formData.get('name') as string;
  const email = formData.get('email') as string;
  const message = formData.get('message') as string;

  // Save to database
  await db.contacts.create({
    data: {
      name,
      email,
      message,
      createdAt: new Date(),
    },
  });

  // Send notification email
  await sendEmail({
    to: 'support@example.com',
    subject: `New contact from ${name}`,
    body: message,
  });

  return { success: true };
}

// ✅ Extract fields from FormData
// ✅ Process data (save + email)
// ✅ Return response
app/contact/page.tsx
import { submitContact } from '@/app/actions/contact';

export default function ContactPage() {
  return (
    <div className="max-w-2xl mx-auto p-8">
      <h1 className="text-3xl font-bold mb-8">Contact Us</h1>

      <form action={submitContact} className="space-y-6">
        {/* Name field */}
        <div>
          <label htmlFor="name" className="block font-semibold mb-2">
            Name
          </label>
          <input
            type="text"
            id="name"
            name="name"
            required
            className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
          />
        </div>

        {/* Email field */}
        <div>
          <label htmlFor="email" className="block font-semibold mb-2">
            Email
          </label>
          <input
            type="email"
            id="email"
            name="email"
            required
            className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
          />
        </div>

        {/* Message field */}
        <div>
          <label htmlFor="message" className="block font-semibold mb-2">
            Message
          </label>
          <textarea
            id="message"
            name="message"
            required
            rows={5}
            className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
          />
        </div>

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

// ✅ action={submitContact} connects form to Server Action
// ✅ name attributes match FormData keys
// ✅ Works without JavaScript
// ✅ Accessible with labels and htmlFor

Progressive Enhancement in Action

This form works perfectly even with JavaScript disabled! The browser handles the submission, Server Action processes it, and the page refreshes. With JavaScript enabled, you can add loading states and client-side enhancements.

Handling Different Input Types

Text Inputs

TYPESCRIPT
// Server Action
export async function createPost(formData: FormData) {
  const title = formData.get('title') as string;
  const slug = formData.get('slug') as string;
  const excerpt = formData.get('excerpt') as string;
  
  // Use values...
}

// Form
<input type="text" name="title" placeholder="Post title" />
<input type="text" name="slug" placeholder="post-slug" />
<textarea name="excerpt" placeholder="Brief description" />

// ✅ Text inputs as strings
// ✅ Textarea handled same way

Checkboxes

TYPESCRIPT
// Server Action
export async function createPost(formData: FormData) {
  // Checkbox is 'on' if checked, absent if unchecked
  const published = formData.get('published') === 'on';
  const featured = formData.get('featured') === 'on';
  
  await db.posts.create({
    data: {
      title: formData.get('title') as string,
      published,
      featured,
    },
  });
}

// Form
<label className="flex items-center gap-2">
  <input type="checkbox" name="published" />
  <span>Publish immediately</span>
</label>

<label className="flex items-center gap-2">
  <input type="checkbox" name="featured" />
  <span>Feature this post</span>
</label>

// ✅ Check with === 'on'
// ✅ Returns 'on' if checked, absent if not
// ✅ Convert to boolean

Radio Buttons

TYPESCRIPT
// Server Action
export async function createPost(formData: FormData) {
  const status = formData.get('status') as string;
  // 'draft', 'published', or 'archived'
  
  await db.posts.create({
    data: {
      title: formData.get('title') as string,
      status,
    },
  });
}

// Form
<div className="space-y-2">
  <label className="flex items-center gap-2">
    <input type="radio" name="status" value="draft" defaultChecked />
    <span>Draft</span>
  </label>
  <label className="flex items-center gap-2">
    <input type="radio" name="status" value="published" />
    <span>Published</span>
  </label>
  <label className="flex items-center gap-2">
    <input type="radio" name="status" value="archived" />
    <span>Archived</span>
  </label>
</div>

// ✅ Same name for all radios
// ✅ Different values
// ✅ Only checked value submitted

Select Dropdowns

TYPESCRIPT
// Server Action
export async function createPost(formData: FormData) {
  const category = formData.get('category') as string;
  
  await db.posts.create({
    data: {
      title: formData.get('title') as string,
      category,
    },
  });
}

// Single select
<select name="category" required className="px-4 py-2 border rounded">
  <option value="">Select category</option>
  <option value="tech">Technology</option>
  <option value="design">Design</option>
  <option value="business">Business</option>
</select>

// Multiple select
<select name="tags" multiple className="px-4 py-2 border rounded">
  <option value="react">React</option>
  <option value="nextjs">Next.js</option>
  <option value="typescript">TypeScript</option>
</select>

// Get multiple values:
const tags = formData.getAll('tags') as string[];
// ['react', 'nextjs'] if both selected

// ✅ Single select: get()
// ✅ Multiple select: getAll()

Number and Date Inputs

TYPESCRIPT
// Server Action
export async function createEvent(formData: FormData) {
  // Numbers come as strings - parse them
  const price = parseFloat(formData.get('price') as string);
  const capacity = parseInt(formData.get('capacity') as string, 10);
  
  // Dates come as strings - convert to Date
  const eventDate = new Date(formData.get('eventDate') as string);
  
  await db.events.create({
    data: {
      name: formData.get('name') as string,
      price,
      capacity,
      eventDate,
    },
  });
}

// Form
<input type="number" name="price" step="0.01" min="0" />
<input type="number" name="capacity" min="1" />
<input type="date" name="eventDate" />
<input type="datetime-local" name="eventDateTime" />
<input type="time" name="eventTime" />

// ✅ Numbers as strings - parse with parseInt/parseFloat
// ✅ Dates as strings - convert with new Date()
// ✅ Validate parsed values

File Uploads

Single File Upload

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

import { writeFile } from 'fs/promises';
import { join } from 'path';

export async function uploadImage(formData: FormData) {
  const file = formData.get('image') as File;

  if (!file) {
    return { success: false, error: 'No file uploaded' };
  }

  // Validate file type
  if (!file.type.startsWith('image/')) {
    return { success: false, error: 'Only images allowed' };
  }

  // Validate file size (5MB max)
  if (file.size > 5 * 1024 * 1024) {
    return { success: false, error: 'File too large (max 5MB)' };
  }

  // Convert to buffer
  const bytes = await file.arrayBuffer();
  const buffer = Buffer.from(bytes);

  // Generate unique filename
  const filename = `${Date.now()}-${file.name}`;
  const filepath = join(process.cwd(), 'public', 'uploads', filename);

  // Save file
  await writeFile(filepath, buffer);

  return {
    success: true,
    url: `/uploads/${filename}`,
  };
}

// ✅ Extract file with get() as File
// ✅ Validate type and size
// ✅ Convert to buffer and save
// ✅ Return file URL
app/upload/page.tsx
'use client';

import { uploadImage } from '@/app/actions/upload';
import { useState } from 'react';

export default function UploadPage() {
  const [result, setResult] = useState<{ success: boolean; url?: string; error?: string } | null>(null);
  const [preview, setPreview] = useState<string | null>(null);

  function handleFileChange(e: React.ChangeEvent<HTMLInputElement>) {
    const file = e.target.files?.[0];
    if (file) {
      setPreview(URL.createObjectURL(file));
    }
  }

  async function handleSubmit(formData: FormData) {
    const response = await uploadImage(formData);
    setResult(response);
  }

  return (
    <div className="max-w-2xl mx-auto p-8">
      <h1 className="text-3xl font-bold mb-8">Upload Image</h1>

      <form action={handleSubmit} className="space-y-6">
        {/* File input */}
        <div>
          <label className="block font-semibold mb-2">
            Choose Image
          </label>
          <input
            type="file"
            name="image"
            accept="image/*"
            required
            onChange={handleFileChange}
            className="w-full"
          />
        </div>

        {/* Preview */}
        {preview && (
          <div>
            <p className="font-semibold mb-2">Preview:</p>
            <img src={preview} alt="Preview" className="max-w-md rounded-lg" />
          </div>
        )}

        {/* Submit */}
        <button
          type="submit"
          className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
        >
          Upload
        </button>

        {/* Result */}
        {result && (
          <div className={`p-4 rounded-lg ${result.success ? 'bg-green-100' : 'bg-red-100'}`}>
            {result.success ? (
              <>
                <p className="text-green-800 mb-2">Upload successful!</p>
                <img src={result.url} alt="Uploaded" className="max-w-md rounded" />
              </>
            ) : (
              <p className="text-red-800">{result.error}</p>
            )}
          </div>
        )}
      </form>
    </div>
  );
}

// ✅ File input with accept attribute
// ✅ Preview before upload
// ✅ Show uploaded image
// ✅ Error handling

Multiple File Upload

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

export async function uploadMultipleImages(formData: FormData) {
  // Get all files with same name
  const files = formData.getAll('images') as File[];

  if (files.length === 0) {
    return { success: false, error: 'No files uploaded' };
  }

  const uploadedUrls: string[] = [];

  for (const file of files) {
    // Validate each file
    if (!file.type.startsWith('image/')) {
      continue; // Skip non-images
    }

    if (file.size > 5 * 1024 * 1024) {
      continue; // Skip large files
    }

    // Save file
    const bytes = await file.arrayBuffer();
    const buffer = Buffer.from(bytes);
    const filename = `${Date.now()}-${file.name}`;
    const filepath = join(process.cwd(), 'public', 'uploads', filename);
    await writeFile(filepath, buffer);

    uploadedUrls.push(`/uploads/${filename}`);
  }

  return {
    success: true,
    urls: uploadedUrls,
  };
}

// Form with multiple file input:
<input
  type="file"
  name="images"
  accept="image/*"
  multiple
  required
/>

// ✅ getAll() for multiple files
// ✅ Loop and process each
// ✅ Return array of URLs

Complex Form Examples

Example 1: Blog Post Form

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

import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';

export async function createPost(formData: FormData) {
  // Extract all fields
  const title = formData.get('title') as string;
  const slug = formData.get('slug') as string;
  const content = formData.get('content') as string;
  const excerpt = formData.get('excerpt') as string;
  const category = formData.get('category') as string;
  const tags = formData.getAll('tags') as string[];
  const published = formData.get('published') === 'on';
  const featured = formData.get('featured') === 'on';

  // Handle featured image
  const imageFile = formData.get('featuredImage') as File;
  let imageUrl = null;

  if (imageFile && imageFile.size > 0) {
    // Save image and get URL
    imageUrl = await saveImage(imageFile);
  }

  // Create post
  const post = await db.posts.create({
    data: {
      title,
      slug,
      content,
      excerpt,
      category,
      tags,
      published,
      featured,
      featuredImage: imageUrl,
      publishedAt: published ? new Date() : null,
    },
  });

  // Revalidate blog page
  revalidatePath('/blog');

  // Redirect to new post
  redirect(`/blog/${post.slug}`);
}

// ✅ Handles all input types
// ✅ Optional file upload
// ✅ Revalidation and redirect
app/create-post/page.tsx
import { createPost } from '@/app/actions/posts';

export default function CreatePostPage() {
  return (
    <div className="max-w-4xl mx-auto p-8">
      <h1 className="text-3xl font-bold mb-8">Create New Post</h1>

      <form action={createPost} className="space-y-6">
        {/* Title */}
        <div>
          <label htmlFor="title" className="block font-semibold mb-2">
            Title
          </label>
          <input
            type="text"
            id="title"
            name="title"
            required
            className="w-full px-4 py-2 border rounded-lg"
          />
        </div>

        {/* Slug */}
        <div>
          <label htmlFor="slug" className="block font-semibold mb-2">
            Slug
          </label>
          <input
            type="text"
            id="slug"
            name="slug"
            required
            placeholder="post-url-slug"
            className="w-full px-4 py-2 border rounded-lg"
          />
        </div>

        {/* Content */}
        <div>
          <label htmlFor="content" className="block font-semibold mb-2">
            Content
          </label>
          <textarea
            id="content"
            name="content"
            required
            rows={15}
            className="w-full px-4 py-2 border rounded-lg font-mono"
          />
        </div>

        {/* Excerpt */}
        <div>
          <label htmlFor="excerpt" className="block font-semibold mb-2">
            Excerpt
          </label>
          <textarea
            id="excerpt"
            name="excerpt"
            rows={3}
            className="w-full px-4 py-2 border rounded-lg"
          />
        </div>

        {/* Category */}
        <div>
          <label htmlFor="category" className="block font-semibold mb-2">
            Category
          </label>
          <select
            id="category"
            name="category"
            required
            className="w-full px-4 py-2 border rounded-lg"
          >
            <option value="">Select category</option>
            <option value="tech">Technology</option>
            <option value="design">Design</option>
            <option value="business">Business</option>
          </select>
        </div>

        {/* Tags */}
        <div>
          <label className="block font-semibold mb-2">Tags</label>
          <div className="space-y-2">
            <label className="flex items-center gap-2">
              <input type="checkbox" name="tags" value="react" />
              <span>React</span>
            </label>
            <label className="flex items-center gap-2">
              <input type="checkbox" name="tags" value="nextjs" />
              <span>Next.js</span>
            </label>
            <label className="flex items-center gap-2">
              <input type="checkbox" name="tags" value="typescript" />
              <span>TypeScript</span>
            </label>
          </div>
        </div>

        {/* Featured Image */}
        <div>
          <label htmlFor="featuredImage" className="block font-semibold mb-2">
            Featured Image
          </label>
          <input
            type="file"
            id="featuredImage"
            name="featuredImage"
            accept="image/*"
            className="w-full"
          />
        </div>

        {/* Options */}
        <div className="space-y-2">
          <label className="flex items-center gap-2">
            <input type="checkbox" name="published" />
            <span>Publish immediately</span>
          </label>
          <label className="flex items-center gap-2">
            <input type="checkbox" name="featured" />
            <span>Feature this post</span>
          </label>
        </div>

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

// ✅ Complete post creation form
// ✅ All input types covered
// ✅ File upload included
// ✅ Works without JavaScript

Example 2: Registration Form

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

import { hash } from 'bcrypt';
import { redirect } from 'next/navigation';

export async function register(formData: FormData) {
  const name = formData.get('name') as string;
  const email = formData.get('email') as string;
  const password = formData.get('password') as string;
  const confirmPassword = formData.get('confirmPassword') as string;
  const agreeToTerms = formData.get('agreeToTerms') === 'on';

  // Validate passwords match
  if (password !== confirmPassword) {
    return {
      success: false,
      error: 'Passwords do not match',
    };
  }

  // Validate terms agreement
  if (!agreeToTerms) {
    return {
      success: false,
      error: 'You must agree to the terms',
    };
  }

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

  if (existingUser) {
    return {
      success: false,
      error: 'Email already registered',
    };
  }

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

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

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

// ✅ Password validation
// ✅ Terms agreement check
// ✅ Duplicate email check
// ✅ Password hashing
// ✅ Redirect after success

Forms Project Structure

Organization of forms with Server Actions

appImportant

Select a file or folder to see details

Form Handling Best Practices

1. Use Semantic HTML

TYPESCRIPT
// ✅ GOOD: Semantic, accessible form
<form action={submitForm}>
  <label htmlFor="email">Email</label>
  <input
    type="email"
    id="email"
    name="email"
    required
    aria-describedby="email-help"
  />
  <span id="email-help">We'll never share your email</span>
  <button type="submit">Submit</button>
</form>

// ❌ BAD: Non-semantic, inaccessible
<div onClick={handleSubmit}>
  <div>Email</div>
  <div contentEditable />
  <div>Submit</div>
</div>

// Use proper form elements for accessibility

2. Always Use name Attributes

TYPESCRIPT
// ✅ GOOD: name attribute for FormData
<input name="email" type="email" />

// ❌ BAD: No name attribute
<input type="email" />
// Won't appear in FormData!

// name attribute is required for FormData extraction

3. Provide Visual Feedback

TYPESCRIPT
// ✅ GOOD: Focus states and validation feedback
<input
  type="email"
  name="email"
  required
  className="border rounded px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:outline-none"
  aria-invalid={hasError}
/>
{hasError && (
  <span className="text-red-600 text-sm">
    Please enter a valid email
  </span>
)}

// Visual feedback improves UX

4. Handle Both Success and Error States

TYPESCRIPT
// ✅ GOOD: Complete error handling
export async function submitForm(formData: FormData) {
  try {
    // Validate
    if (!formData.get('email')) {
      return { success: false, error: 'Email required' };
    }

    // Process
    await processData(formData);

    return { success: true, message: 'Success!' };
  } catch (error) {
    return { success: false, error: 'Something went wrong' };
  }
}

// Handle all cases gracefully

5. Use Proper Input Types

TYPESCRIPT
// ✅ GOOD: Correct input types
<input type="email" name="email" />     // Email validation
<input type="tel" name="phone" />       // Phone keyboard on mobile
<input type="url" name="website" />     // URL validation
<input type="number" name="age" />      // Number keyboard
<input type="date" name="birthdate" />  // Date picker

// ❌ BAD: Generic text for everything
<input type="text" name="email" />
<input type="text" name="phone" />

// Proper types improve UX and validation

Key Takeaways

  • action attribute - connects form to Server Action
  • FormData - automatic data collection from form fields
  • Progressive enhancement - works without JavaScript
  • name attributes - required for FormData keys
  • File uploads - extract with get() as File
  • Multiple values - use getAll() for arrays
  • Accessibility - labels, htmlFor, ARIA attributes
  • Type parsing - numbers and dates come as strings

What's Next?

You've mastered form handling with Server Actions! Next, we'll explore Form Validation and Error Handling—adding robust validation with Zod, displaying field-specific errors, implementing progressive validation, and creating user-friendly error experiences. You'll build forms that guide users to success!

We'll cover schema validation, error display patterns, inline validation, and building production-grade form validation systems.

♿ Accessibility Matters

Always use semantic HTML, labels, and proper ARIA attributes. Accessible forms work for everyone—keyboard users, screen readers, and users with disabilities. Good accessibility is good UX for all users!

Test Your Understanding

Question 1 of 4

What makes a form progressively enhanced?

Master form handling with Server Actions in Next.js! Learn progressive enhancement, FormData, and file uploads.

Previous
Understanding Server Actions
Next
Form Validation and Error Handling

Master Next.js Forms

Join 2,000+ developers building powerful Next.js forms. Get the next lesson on form validation and error handling - 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