Loading...
본문 바로가기
👥
총 방문자
📖
0개 이상
총 포스팅
🧑
오늘 방문자 수
📅
0일째
블로그 운영

여러분의 방문을 환영해요! 🎉

다양한 개발 지식을 쉽고 재미있게 알려드리는 블로그가 될게요. 함께 성장해요! 😊

코딩 정보/NextJs

[개념부터 Nextjs] 서버 액션에 대해 자세히 알아보자

by 꽁이꽁설꽁돌 2025. 8. 15.
728x90
반응형
     

목차

     

     

    서버액션

    서버액션은 nextjs 애플리케이션에서 폼 제출 및 데이터 변조를 처리하기 위해 서버에서 실행되는 비동기 함수이다.

    이들은 server 및 client components에서 호출될 수 있다.

     

     

    서버함수

    서버에서 실행되는 비동기 함수로 클라이언트에서 네트워크 요청을 통해 호출되기 때문에 비동기로 작성되어야 한다.

    서버 액션은 startTransition과 함계 사용되는 async 함수를 의미한다. 

     

    다음의 경우에는 자동으로 그렇게 동작한다.

     

    1. form의 action prop으로 함수를 전달한 경우

    2. button의 formAction으로 prop으로 함수를 전달한 경우

     

    서버 액션은 프레임워크의 캐싱 아키텍쳐와 통합되어 액션이 호출되면, next.js는 업데이트된 ui와 새로운 데이터를

    서버 왕복 한 번에 함께 반환할 수 있다.

     

     

    서버액션의 장점

    1. 효율적인 랜더링

    Server Action 호출 시, Next.js는 다음과 같은 단계를 거친다

    1. 클라이언트에서 액션 호출:
      Server Action 함수가 호출되면, 브라우저는 이 요청을 POST 메서드로 서버에 전달한다. (모든 요청에 대해 rsc payload를 통해 post 요청한다.)
    2. 서버에서 데이터 처리:
      외부 API를 호출한다. 이 과정에서 필요한 로직과 데이터를 서버에서 처리한다.
    3. 업데이트된 데이터와 UI 동기화:
      서버는 처리된 결과를 기반으로 클라이언트에게 새로운 데이터를 반환한다.
      동시에 서버는 해당 데이터로 렌더링된 업데이트된 UI를 생성하여 클라이언트로 전달한다.
    4. 클라이언트 갱신:
      클라이언트는 서버에서 반환된 데이터와 UI를 받아 새롭게 렌더링한다.
      이때 페이지 리로드 없이도 화면이 갱신된다.

    이에 대한 장점은

    • 클라이언트와 서버 간의 요청/응답이 한 번만 발생하므로 네트워크 지연 시간이 줄어든다.
    • 서버는 처리된 결과를 기반으로 데이터를 갱신하고 UI까지 렌더링하기 때문에 클라이언트가 따로 데이터를 받고 UI를 다시 그릴 필요가 없다.

     

    2. 캐싱 및 재검증

    server action을 사용하면 next의 cache 기능들을 활용하고 재검증할 수 있습니다. 하지만 server action을 사용하지 않으면 캐시를 재검증할 방법이 없다.

    따로 클라이언트 컴포넌트에서 서버액션을 사용하지 않으면 리액트 쿼리같은 라이브러리를 통해 처리하거나 직접 구현해 주어야 한다.

     

     

    서버 컴포넌트일 때

    서버액션은 인라인 함수 레벨 또는 모듈 레벨의 use server 지시어를 사용할 수 있다.

    export default function Page() {
      // Server Action
      async function create() {
        'use server'
        // 데이터 변조
      }
     
      return '...'
    }

     

    클라이언트 컴포넌트일 때

    파일 상단에 use server 지시어를 추가해 준다. 

    파일 내의 모든 함수는 server 및 client 컴포넌트에서 재사용할 수 있는 서버 액션으로 표시된다.

    'use server'
     
    export async function create() {}

     

    'use client'
     
    import { create } from '@/app/actions'
     
    export function Button() {
      return <Button onClick={create} />
    }

     

    프롭스로 넘기기

    서버 액션을 클라이언트 컴포넌트에 프롭으로 전달할 수 있다.

    'use client'
     
    export default function ClientComponent({
      updateItemAction,
    }: {
      updateItemAction: (formData: FormData) => void
    }) {
      return <form action={updateItemAction}>{/* ... */}</form>
    }

     

     

    ++추가 정보

    보통 Next.js TypeScript 플러그인은 client-component.tsx에서 updateItemAction이 직렬화할 수 없는 함수이기 때문에 이를 플래그로 표시합니다. 하지만 action 또는 Action으로 끝나는 props는 Server Actions를 받는 것으로 간주됩니다. 이것은 TypeScript 플러그인이 실제로 Server Action인지 일반 함수인지 알지 못하기 때문에 사용하는 추측입니다. 런타임 타입 체크는 실수로 Client Component에 함수를 전달하지 않도록 보장합니다.

     

    -> 무슨 말이 잘 와닿지 않는다. 천천히 하나하나 이해해보자

     

    직렬화

    직렬화는 데이터 스토리지 문맥에서 데이터 구조나 오브젝트 상태를 동일하거나 다른 컴퓨터 환경에 저장하고 나중에 재구성할 수 있는 포맷으로 변환하는 과정을 말한다.

    우리가 흔히 사용하는 JSON.stringify()함수가 직렬화를 수행하는 함수이며, 반대로 JSON.parse() 함수가 역직렬화를 수행하는 함수이다.

     

    -> 주의할 점

    모든 객체를 직렬화할 수 없다.

    대표적으로 function 함수는 직렬화가 불가능한 객체이다.

     

    function이 실행 코드와 실행 컨텍스트를 모두 포함하는 개념이기 때문인데, 함수는 자신이 선언된 스코프에 대한 참조를 유지하고, 그 시점의 외부 변수에 대한 참조를 기억하고 있다. js의 클로저가 바로 이런 현상을 가리키는 용어이다.

     

     

    1. 함수는 직렬화가 불가 하기 때문에 서버에서 클라이언트 프롭스로 함수를 넘기는 것은 불가능하다.

    2. use server가 붙은 Server Action은 Next가 “특수한 참조”로 다뤄서 props로 넘겨도 된다.

    3. 컴파일 단계에서 TS 플러그인은 이 함수가 진짜 서버 액션인지 알 수 없다. 그래서 prop 이름이 action/Action으로 끝나면 서버 액션일 거라 가정하고 경고를 안 낸다.

    4. 런타임이 최종적으로 판단한다.

     

    form에서 서버 액션 사용하기

    export default function Page() {
      async function createInvoice(formData: FormData) {
        'use server'
     
        const rawFormData = {
          customerId: formData.get('customerId'),
          amount: formData.get('amount'),
          status: formData.get('status'),
        }
     
        // 데이터 변조
        // 캐시 재검증
      }
     
      return <form action={createInvoice}>...</form>
    }

     

     

    JavaScript bind 메서드를 사용하여 Server Action에 추가 인수를 전달할 수 있습니다.

    'use client'
     
    import { updateUser } from './actions'
     
    export function UserProfile({ userId }: { userId: string }) {
      const updateUserWithId = updateUser.bind(null, userId)
     
      return (
        <form action={updateUserWithId}>
          <input type="text" name="name" />
          <button type="submit">Update User Name</button>
        </form>
      )
    }

     

    이에 대한 장점은 다음과 같다.

     

    js로 구현한 form

    // server action을 사용하지 않는 경우
    'use client';
    
    export default function FormWithEnhancements() {
      const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        const formData = new FormData(e.currentTarget);
        const response = await fetch("/api/submit", {
          method: "POST",
          body: formData,
        });
        console.log(await response.json());
      };
    
      return (
        <form onSubmit={handleSubmit}>
          <label htmlFor="name">Name:</label>
          <input id="name" name="name" type="text" />
          <button type="submit">Submit</button>
        </form>
      );
    }

     

    위 코드는 js 번들을 다 다운받기 전까지는 실행할 수 없다.

     

    서버 액션으로 구현한 폼

    // server action을 사용하는 경우
    export default function Page() {
      async function handleFormSubmit(formData: FormData) {
        "use server";
        const name = formData.get("name");
        console.log(`Name submitted: ${name}`);
      }
    
      return (
        <form action={handleFormSubmit}>
          <label htmlFor="name">Name:</label>
          <input id="name" name="name" type="text" />
          <button type="submit">Submit</button>
        </form>
      );
    }

     

     

    위와 같이 handleFormSubmit server action 함수가 있다.
    Javascript가 로드되지 않았더라도 form의 action을 통해 form 제출이 가능하다.

    그리고 handleFormSubmit함수는 브라우저가 아닌 Next 서버에서 실행된다. 위 함수가 서버에서 실행되기 때문에 브라우저에 Javascript가 로드되지 않더라도 사용할 수 있다.

    이처럼 server action을 사용하면 Javascript가 로드되지 않더라도 폼 제출이 가능하다는 장점이 있다.

     

     

     

    라우터 핸들러

    처음에 라우터 핸들러가 왜 필요한지 전혀 납득이 가지 않아 검색을 해보았고 다음과 같은 이유로 존재한다.

     

    1. 클라이언트 컴포넌트에서 내부 db를 호출할 때 안전하게 호출할 수 있다. 

    따라서 외부 api만 호출할 때는 굳이 사용할 필요가 없다.

     

    -> mvc 패턴의 controller 역할을 하여 깔끔하게 분리할 수 있다.

     

    2. nextjs12 이하의 api 라우트를 대체하기 위해 만들어진 것이다. 이때 서버 액션과 다른점은 둘다 내부적으로 엔드포인트를 만들지만 라우터 핸들러는 파일로 존재하고 서버액션은 프레임워크에서 동적으로 매핑된다.

     

     

     

    출처

    https://nextjs.org/docs/app/getting-started/updating-data#behavior

     

    Getting Started: Updating Data | Next.js

    Learn how to mutate data using Server Functions.

    nextjs.org

     

    https://velog.io/@kcj_dev96/Server-Action

     

    Server Action 왜 사용할까?

    프로젝트를 진행하다보면 클라이언트에서 백엔드 API로 data mutation(HTTP POST,PATCH,PUT,DELETE)를 할 일이 발생합니다.하지만 nextjs에서는 위 방법으로써 Server Action 기능을 제안하고 있는데요.Server Action

    velog.io

     

    반응형