0Pricing

Next.js 15 Fullstack: Your First Dive into App Router & Server Actions

Discover the power of Next.js 15, learning how App Router and Server Actions revolutionize fullstack development. This introductory guide walks you through setting up your first project and building a simple application with these game-changing features.

N
Next.js 15 Fullstack (App Router + Server Actions) · 9 min read · 1,781 words

Welcome, CoddyKit learners! The landscape of web development is constantly evolving, pushing the boundaries of what's possible and streamlining the developer experience. For years, the dream of a truly seamless fullstack development workflow – where frontend and backend logic feel intrinsically linked – has been a holy grail. With Next.js 15, that dream is not just closer; it's practically a reality.

This is the first in a five-part series where we'll explore Next.js 15's revolutionary features, focusing on the App Router and Server Actions, to build robust, performant, and delightful fullstack applications. In this inaugural post, we're diving straight into the deep end: getting started, understanding the core concepts, and building your first fullstack application.

The Fullstack Revolution: App Router and Server Actions

Next.js has always been at the forefront of React development, offering powerful features like server-side rendering and static site generation. Next.js 15, however, takes this to an entirely new level by doubling down on React's Server Components and introducing Server Actions. These two pillars form the foundation of a paradigm shift towards a truly integrated fullstack experience.

What is the App Router?

The App Router, introduced in Next.js 13 and now stable and enhanced in Next.js 15, is a new routing and data fetching paradigm built on React Server Components. It rethinks how we structure applications, moving away from the traditional pages/ directory to an app/ directory. Here's why it's a game-changer:

  • Server Components by Default: Most components within the app/ directory are Server Components by default. This means they render on the server, can directly access backend resources (like databases or file systems), and send only the necessary HTML and CSS to the client, resulting in smaller JavaScript bundles and faster initial page loads.
  • Colocation: You can colocate files like components, styles, tests, and even API logic (via Server Actions) within the same route segment, making your codebase more organized and easier to maintain.
  • Advanced Data Fetching: The App Router provides powerful data fetching capabilities, including automatic request memoization, revalidation, and streaming UI, allowing parts of your page to load progressively.

What are Server Actions?

Server Actions are perhaps the most exciting new feature for fullstack developers. They allow you to define server-side functions that can be directly invoked from your client-side components, forms, or even other Server Components. Think of it as calling a backend function without having to write an explicit API endpoint!

  • Direct Server Interaction: No more creating separate API routes (e.g., /api/todos) for simple data mutations or form submissions. You can define a function with "use server" and call it directly.
  • Type Safety: With TypeScript, Server Actions provide end-to-end type safety, from your client component's form data to your database interaction.
  • Simplified Mutations: They significantly simplify handling form submissions, data updates, and other server-side operations, reducing boilerplate and improving developer experience.
  • Automatic Revalidation: Server Actions can automatically revalidate cached data, ensuring your UI reflects the latest changes without manual intervention.

Getting Started: Your First Next.js 15 Project

Let's roll up our sleeves and create a new Next.js 15 project. Make sure you have Node.js (v18.17 or higher) installed on your machine.

1. Create a New Project

Open your terminal and run the following command:

npx create-next-app@latest coddykit-next15-app

The installer will prompt you with a series of questions. For this guide, we recommend the following choices:

  • Would you like to use TypeScript? Yes
  • Would you like to use ESLint? Yes
  • Would you like to use Tailwind CSS? No (or Yes, if you prefer)
  • Would you like to use src/ directory? No
  • Would you like to use App Router? (recommended) Yes
  • Would you like to customize the default import alias? No

2. Run the Development Server

Navigate into your new project directory and start the development server:

cd coddykit-next15-app
npm run dev
# or
yarn dev
# or
pnpm dev

Open http://localhost:3000 in your browser, and you should see the default Next.js welcome page.

A Quick Tour of the App Router

When you open your project, you'll notice the new app/ directory. This is where all your routes and components will live.

Understanding Layouts and Pages

  • app/layout.tsx: This is the root layout for your application. It defines the shared UI for all routes, like your <html> and <body> tags, navigation bars, footers, and global metadata. It's a Server Component by default.

    // app/layout.tsx
    import type { Metadata } from 'next';
    import './globals.css';
    
    export const metadata: Metadata = {
      title: 'CoddyKit Next.js 15 App',
      description: 'Exploring Next.js 15 App Router and Server Actions',
    };
    
    export default function RootLayout({ children }: { children: React.ReactNode }) {
      return (
        <html lang="en">
          <body>
            <header>
              <nav>CoddyKit Next.js</nav>
            </header>
            <main>{children}</main>
            <footer>
              <p>&copy; {new Date().getFullYear()} CoddyKit</p>
            </footer>
          </body>
        </html>
      );
    }
  • app/page.tsx: This file defines the UI for the root route (/). Any page.tsx file within a folder (e.g., app/dashboard/page.tsx for /dashboard) will render the UI for that specific route segment. It's also a Server Component by default.

    // app/page.tsx
    export default function HomePage() {
      return (
        <div>
          <h1>Welcome to CoddyKit's Next.js 15 Fullstack Journey!</h1>
          <p>This is our introductory page. Let's build something amazing.</p>
        </div>
      );
    }

Server Components vs. Client Components

Remember, components in app/ are Server Components by default. This is great for performance and SEO, but what if you need client-side interactivity (state, event listeners, browser APIs)? That's where Client Components come in.

To mark a component as a Client Component, add the "use client" directive at the very top of the file:

// app/components/Counter.tsx
"use client"; // <-- This directive makes it a Client Component

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times.</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

You can then import and use this Counter component within a Server Component (like app/page.tsx). Next.js automatically handles the hydration process.

Unleashing Server Actions: A Simple Todo App

Let's create a minimal todo application to see Server Actions in action. We'll have a page to display todos and a form to add new ones.

1. Create a Todos Page

First, create a new route segment for our todos. Create a folder app/todos and inside it, a page.tsx file:

// app/todos/page.tsx
import AddTodoForm from './add-todo-form';

// For simplicity, we'll use an in-memory array for now.
// In a real app, this would be a database call.
interface Todo {
  id: string;
  text: string;
  completed: boolean;
}

let todos: Todo[] = [
  { id: '1', text: 'Learn Next.js 15', completed: false },
  { id: '2', text: 'Master Server Actions', completed: false },
];

export default function TodosPage() {
  return (
    <div>
      <h1>My Todos</h1>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
      <h2>Add a New Todo</h2>
      <AddTodoForm />
    </div>
  );
}

Notice how todos is just a simple array for now. In a real application, this data fetching would happen asynchronously, perhaps from a database. Because app/todos/page.tsx is a Server Component, it can directly fetch data on the server before rendering.

2. Define a Server Action

Next, let's create our Server Action. Create a file named app/todos/actions.ts:

// app/todos/actions.ts
"use server"; // <-- This directive is crucial for Server Actions

import { revalidatePath } from 'next/cache';

interface Todo {
  id: string;
  text: string;
  completed: boolean;
}

// This should ideally be a database interaction
let todos: Todo[] = [
  { id: '1', text: 'Learn Next.js 15', completed: false },
  { id: '2', text: 'Master Server Actions', completed: false },
];

export async function addTodo(formData: FormData) {
  const text = formData.get('todoText') as string;

  if (!text) {
    return { error: 'Todo text cannot be empty.' };
  }

  const newTodo: Todo = {
    id: String(todos.length + 1),
    text,
    completed: false,
  };
  todos.push(newTodo);

  console.log('New todo added:', newTodo);

  // Revalidate the /todos path to show the new todo immediately
  revalidatePath('/todos');

  return { success: true, todo: newTodo };
}

Key takeaways from this file:

  • The "use server" directive at the top.
  • The addTodo function is async because server-side operations are typically asynchronous.
  • It receives a FormData object when invoked from a <form> element.
  • revalidatePath('/todos') tells Next.js to clear the cache for the /todos route, ensuring that the updated todos list is fetched and displayed on the next render.

3. Create the Add Todo Form (Client Component)

Now, let's create a Client Component for our form that will invoke the addTodo Server Action. Create app/todos/add-todo-form.tsx:

// app/todos/add-todo-form.tsx
"use client"; // <-- This form needs client-side interactivity

import { useRef } from 'react';
import { addTodo } from './actions'; // Import the Server Action

export default function AddTodoForm() {
  const formRef = useRef<HTMLFormElement>(null);

  const handleSubmit = async (formData: FormData) => {
    const result = await addTodo(formData);
    if (result.error) {
      alert(result.error);
    } else {
      // Clear the form after successful submission
      formRef.current?.reset();
    }
  };

  return (
    <form ref={formRef} action={handleSubmit}>
      <input type="text" name="todoText" placeholder="Add a new todo" required />
      <button type="submit">Add Todo</button>
    </form>
  );
}

In this Client Component:

  • We import the addTodo Server Action directly.
  • The <form> element's action prop is set to our handleSubmit function, which in turn calls the Server Action. When a form uses an action, Next.js automatically serializes the form data and sends it to the Server Action.
  • After a successful submission, we reset the form using a ref.

Now, navigate to http://localhost:3000/todos. You should see your initial todos and a form. Try adding a new todo, and watch it appear instantly without a full page refresh!

Bringing It All Together: The Fullstack Vision

What we've just built is a truly fullstack application with minimal effort:

  • The app/todos/page.tsx (a Server Component) fetches and renders the initial list of todos on the server.
  • The AddTodoForm.tsx (a Client Component) provides the interactive UI.
  • The addTodo function (a Server Action) handles the server-side logic for adding a todo, directly invoked from the client-side form.

This seamless integration means less context switching between frontend and backend concepts. You're writing JavaScript/TypeScript for both, leveraging the same tooling and type safety across your entire application. It's a significant leap forward in developer productivity and application performance.

Conclusion

Next.js 15, with its enhanced App Router and powerful Server Actions, is ushering in a new era of fullstack web development. We've just scratched the surface, but you've already seen how to set up a project, navigate the App Router, and build a simple interactive application using Server Actions. This foundation is crucial for understanding the advanced patterns and best practices we'll cover in the rest of this series.

Ready to dive deeper? In our next post, we'll explore Best Practices and Tips for working with Next.js 15's App Router and Server Actions, helping you write cleaner, more performant, and maintainable code. Stay tuned!

ProgrammingTutorialCoddyKit

Enjoyed this article?

Explore more tutorials and insights to level up your coding skills.

Browse All Articles →