DEV Community

Cover image for NextJs Server Actions: Why and How
Harshal Ranjhani for CodeParrot

Posted on • Originally published at codeparrot.ai

NextJs Server Actions: Why and How

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
Enter fullscreen mode Exit fullscreen mode
  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
Enter fullscreen mode Exit fullscreen mode
  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' }
  });
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  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);
}
Enter fullscreen mode Exit fullscreen mode

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
  )
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

When to use getStaticProps:

  • The data required to render the page is available at build time ahead of a user’s request.
  • The data comes from a headless CMS
  • The page must be pre-rendered (for SEO) and be very fast — getStaticProps generates HTML and JSON files, both of which can be cached by a CDN for performance.
  • The data can be publicly cached (not user-specific). This condition can be bypassed in certain specific situation by using a Middleware to rewrite the path.

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
}
Enter fullscreen mode Exit fullscreen mode

When to use getStaticPaths:

  • The data comes from a headless CMS
  • The data comes from a database
  • The data comes from the filesystem
  • The data can be publicly cached (not user-specific)
  • The page must be pre-rendered (for SEO) and be very fast — getStaticProps generates HTML and JSON files, both of which can be cached by a CDN for performance.

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.