NextJs Server Actions: Why and How

August 1, 2024 (1mo ago)

What is a Server Action?

A server action is a function that runs on the server side of an application. It is used to fetch data from an API and render it on the server side. This is useful for SEO purposes because it allows search engines to index the content of your website.

What are NextJs Server Actions?

NextJs is a React framework that allows you to build server-side rendered applications. NextJs server actions are functions that run on the server side of a NextJs application. They allow you to execute server-side code during the request lifecycle, giving you the ability to perform operations like data fetching, authentication, and other server-side logic seamlessly within your application.

At their core, server actions are just functions that return a promise. They can be used to fetch data from an API, query a database, or perform any other server-side operation that you need to do during the request lifecycle.

Why are NextJs Server Actions Needed?

The introduction of Server Actions addresses several pain points in modern web development:

  1. Simplified data mutations: Traditional approaches often require setting up separate API routes and managing state on the client-side. Server Actions streamline this process by allowing direct server-side operations from client components.

  2. Reduced client-side JavaScript: By moving certain operations to the server, the amount of JavaScript that needs to be sent to the client is reduced, improving initial load times and overall performance.

  3. Enhanced security: Server Actions run on the server, making it easier to implement secure operations without exposing sensitive logic or data to the client.

  4. Improved developer experience: With Server Actions, developers can write server-side logic alongside their client components, leading to a more cohesive and maintainable codebase.

  5. Better error handling: Server Actions provide a more straightforward way to handle and display server-side errors without the need for complex state management on the client.

Setting Up NextJs Server Actions

Using the App Router

  1. Install Next.js: If you haven’t already, you can create a new Next.js project by running:
npx create-next-app@latest my-next-app
cd my-next-app
npm run dev
  1. Create a New API Route: In the app directory, create a new folder called api and then create a new file called [action].js:
mkdir -p app/api
touch app/api/[action].js
  1. Export a server action: In your new file, export an async function that will handle the server-side logic. For example:
export async function GET(request) {
  const data = await fetchDataFromAPI();
  return new Response(JSON.stringify(data), {
    headers: { 'Content-Type': 'application/json' }
  });
}

Using the Pages Router

  1. Install Next.js: If you haven’t already, you can create a new Next.js project by running:
npx create-next-app@latest my-next-app
cd my-next-app
npm run dev

Follow the instructions to create a new Next.js project with the pages router.

  1. Create a New API Route: In the pages/api directory, create a new file called action.js:
mkdir -p pages/api
touch pages/api/action.js
  1. Define the Server Action: In your new file, export an async function that will handle the server-side logic. For example:
export default async function handler(req, res) {
  const data = await fetchDataFromAPI();
  res.status(200).json(data);
}

Creating and using NextJs Server Actions

Using the App Router

NextJs provides a convention to define server actions with the React "use server"directive. You can place the directive at the top of an async function to mark the function as a server action, or at the top of a separate file to mark all exports of that file as server actions.

Server Components

Server components can use the inline function level or module level "use server" directive. To inline a server action, add "use server" to the top of the function body:

// Server Component
export default function Page() {
  // Server Action
  async function fetchData() {
    'use server'
    const response = await fetch('/api/action');
    const data = await response.json();
    return data;
  }
 
  return (
    // Use fetchData function here
  )
}

Client Components

Client components can only import actions that use the module-level "use server" directive. To call a server action in a client component, create a new file and add the "use server" directive at the top of it. All functions within the file will be marked as server actions that can be reused in both client and server components:

// app/actions.ts
'use server'
 
export async function fetchData() {
  const response = await fetch('/api/action');
  const data = await response.json();
  return data;
}

In a client component, you can now import and use the server action:

// app/ui/button.tsx
import { fetchData } from '@/app/actions'
 
export function Button() {
  const handleClick = async () => {
    const data = await fetchData();
    console.log(data);
  }
 
  return <button onClick={handleClick}>Fetch Data</button>;
}

You can also pass a server action to a client component as a prop:

// app/page.tsx
import { fetchData } from '@/app/actions'
 
export default function Page() {
  return <ClientComponent fetchData={fetchData} />;
}
 
// app/client-component.jsx
'use client'
 
export default function ClientComponent({ fetchData }) {
  const handleClick = async () => {
    const data = await fetchData();
    console.log(data);
  }
 
  return <button onClick={handleClick}>Fetch Data</button>;
}

Using the Pages Router

getStaticProps

To use a server action with the pages router, you can define a getStaticProps function in your page component. This function will run on the server side and fetch data before rendering the page:

import type { InferGetStaticPropsType, GetStaticProps } from 'next'
 
type Repo = {
  name: string
  stargazers_count: number
}
 
export const getStaticProps = (async (context) => {
  const res = await fetch('https://api.github.com/repos/vercel/next.js')
  const repo = await res.json()
  return { props: { repo } }
}) satisfies GetStaticProps<{
  repo: Repo
}>
 
export default function Page({
  repo,
}: InferGetStaticPropsType<typeof getStaticProps>) {
  return repo.stargazers_count
}

When to use getStaticProps:

getStaticProps always runs on the server and never on the client.

getStaticPaths

If a page has Dynamic Routes and uses getStaticProps, it needs to define a list of paths to be statically generated.

When you export a function called getStaticPaths (Static Site Generation) from a page that uses dynamic routes, Next.js will statically pre-render all the paths specified by getStaticPaths.

import type {
  InferGetStaticPropsType,
  GetStaticProps,
  GetStaticPaths,
} from 'next'
 
type Repo = {
  name: string
  stargazers_count: number
}
 
export const getStaticPaths = (async () => {
  return {
    paths: [
      {
        params: {
          name: 'next.js',
        },
      }, // See the "paths" section below
    ],
    fallback: true, // false or "blocking"
  }
}) satisfies GetStaticPaths
 
export const getStaticProps = (async (context) => {
  const res = await fetch('https://api.github.com/repos/vercel/next.js')
  const repo = await res.json()
  return { props: { repo } }
}) satisfies GetStaticProps<{
  repo: Repo
}>
 
export default function Page({
  repo,
}: InferGetStaticPropsType<typeof getStaticProps>) {
  return repo.stargazers_count
}

When to use getStaticPaths:

getStaticPaths will only run during build in production, it will not be called during runtime.

getServerSideProps

getServerSideProps is a Next.js function that can be used to fetch data and render the contents of a page at request time.

Example:

import type { InferGetServerSidePropsType, GetServerSideProps } from 'next'
 
type Repo = {
  name: string
  stargazers_count: number
}
 
export const getServerSideProps = (async () => {
  // Fetch data from external API
  const res = await fetch('https://api.github.com/repos/vercel/next.js')
  const repo: Repo = await res.json()
  // Pass data to the page via props
  return { props: { repo } }
}) satisfies GetServerSideProps<{ repo: Repo }>
 
export default function Page({
  repo,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
  return (
    <main>
      <p>{repo.stargazers_count}</p>
    </main>
  )
}

When to use getServerSideProps:

You should use getServerSideProps if you need to render a page that relies on personalized user data, or information that can only be known at request time. For example, authorization headers or a geolocation.

For client side data fetching, useEffect or SWR is recommended.

Conclusion

NextJs server actions are a powerful feature that allows you to execute server-side code during the request lifecycle. They provide a simple and efficient way to fetch data from an API and render it on the server side, improving SEO and performance. By using server actions, you can streamline your data fetching process, reduce client-side JavaScript, enhance security, and improve your developer experience. Whether you are building a small blog or a large e-commerce site, NextJs server actions can help you build fast, secure, and scalable applications.