기록

Next.js의 서버 액션에서 발생한 "It is not allowed to define inline 'use server'..." 에러 해결하기

als982001 2024. 11. 24. 16:15

1. 개요

 버튼을 클릭하면 해당 상품의 간단한 정보를 모달로 보여주는 기능을 구현하려고 했다. 그래서 이를 위해 다음과 같이 코드를 작성했다.

 

// components/ProductModal.tsx

import { useEffect, useState } from "react";

import { PhotoIcon } from "@heroicons/react/24/solid";
import db from "@/lib/db";

interface IProps {
  id: number;
  handleShoWModal: (e: React.MouseEvent) => void;
}

export default function ProductModal({ id, handleShoWModal }: IProps) {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const [product, setProduct] = useState<any>(null);
  const [isLoading, setIsLoading] = useState(true);

  async function getProduct(id: number) {
    try {
      const product = await db.product.findUnique({
        where: {
          id,
        },
        include: {
          user: {
            select: {
              username: true,
              avatar: true,
            },
          },
        },
      });

      return product;
    } catch (e) {
      console.error(e);
      return null;
    }
  }

  useEffect(() => {
    (async () => {
      const product = await getProduct(id);

      setProduct(product);
      setIsLoading(false);
    })();
  }, [id]);

  if (isLoading) {
    return (
      <div
        className="absolute w-full h-full z-50 flex items-center justify-center bg-black bg-opacity-60 left-0 top-0"
        onClick={handleShoWModal}
      >
        <div className="max-w-screen-sm h-1/2  flex justify-center w-full">
          <div className="aspect-square  bg-neutral-700 text-neutral-200  rounded-md flex justify-center items-center">
            <PhotoIcon className="h-28" />
          </div>
        </div>
      </div>
    );
  }

  if (!product) {
    return <div>없음</div>;
  }

  console.log(product);

  return <div>{`id: ${id}`}</div>;
}

 

 

Error: PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in unknown). If this is unexpected, please open an issue: https://pris.ly/prisma-prisma-bug-report

 

ProductModal이라는 컴포넌트의 부모 컴포넌트는 클라이언트이다. 그렇기에 위와 같은 에러가 발생했다. 이는 PrismaClient가 브라우저 환경에서 실행되었거나 브라우저를 대상으로 번들링되었을 때 발생하는 에러로, 서버 환경이 아닌 곳에서 실행하려 했기에 발생한 에러이다. 그래서 이를 수정하려 했다.

 

2. 에러 발생

// components/ProductModal.tsx

import { useEffect, useState } from "react";

import { PhotoIcon } from "@heroicons/react/24/solid";
import { getProduct } from "@/lib/product";

interface IProps {
  id: number;
  handleShoWModal: (e: React.MouseEvent) => void;
}

export default function ProductModal({ id, handleShoWModal }: IProps) {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const [product, setProduct] = useState<any>(null);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    (async () => {
      const product = await getProduct(id);

      setProduct(product);
      setIsLoading(false);
    })();
  }, [id]);

  if (isLoading) {
    // return 스켈레톤 컴포넌트
  }

  if (!product) {
    // return 상품 정보 없음
  }

  // return 상품 정보
}

 

// lib/product.ts

import db from "./db";

export async function getProduct(id: number) {
  "use server";

  try {
    const product = await db.product.findUnique({
      where: {
        id,
      },
      include: {
        user: {
          select: {
            username: true,
            avatar: true,
          },
        },
      },
    });

    return product;
  } catch (e) {
    console.error(e);
    return null;
  }
}

 

"use server"라는 코드를 추가하는 것으로 서버 액션을 사용할 수 있다고 배웠기에 위와 같이 코드를 작성하였다.

 

 

그랬더니 위 이미지처럼 에러가 발생하였다.

 

3. 원인

 Next.js의 서버 액션(Server Action)은 특정 규칙을 따른다.

  • "use server"는 파일 최상단에 선언되어야 한다.
  • 클라이언트 컴포넌트 내부에서는 "use server"를 사용하여 서버 액션을 정의할 수 없다.
  • 서버 액션은 클라이언트 컴포넌트에서 직접 실행할 수 없으며 RPC(Remote Procedure Call) 형태로 호출된다.

즉, "use server"를 최상단에 선언하지 않았기 때문에 발생한 에러였다. 그렇기에 아래 코드처럼 수정하여 에러를 해결할 수 있었다.

 

"use server";

import db from "./db";

export async function getProduct(id: number) {
  try {
    const product = await db.product.findUnique({
      where: {
        id,
      },
      include: {
        user: {
          select: {
            username: true,
            avatar: true,
          },
        },
      },
    });

    return product;
  } catch (e) {
    console.error(e);
    return null;
  }
}

 


관련 문서

- Next.js Server Actions 공식 문서: https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations