devlog study react nextjs server-action

Next.js Server Action ๐Ÿ“


๋‚ด์–ด๋ณด๊ธฐ

  • ํด๋ผ์ด์–ธํŠธ(Browser๋‚˜ Server Component)์—์„œ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋Š”, ์„œ๋ฒ„์—์„œ ์‹คํ–‰๋˜๋Š” ํ•จ์ˆ˜

    • ์„œ๋ฒ„์—์„œ๋งŒ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๋™์ž‘์„ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ
    • "use server" ์ถ”๊ฐ€ํ•˜๋ฉด ์„œ๋ฒ„์•ก์…˜์œผ๋กœ ์‹คํ–‰๋จ
      • ์„œ๋ฒ„์•ก์…˜์„ ์‹คํ–‰ํ•˜๋Š” API๊ฐ€ ์ƒ์„ฑ๋˜์–ด ๋ธŒ๋ผ์šฐ์ €์—์„œ ํ•ด๋‹น ์„œ๋ฒ„์•ก์…˜์„ ์‹คํ–‰ํ•˜๋Š” ์ž‘์—…์„ ํ–ˆ์„๋•Œ ์ด API๋ฅผ ํ˜ธ์ถœํ•˜๊ฒŒ ๋จ
      • server action์€ ๋ฌด์กฐ๊ฑด server์—์„œ ํ˜ธ์ถœ
  • ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ(์˜ˆ: ์ปดํฌ๋„ŒํŠธ, ํผ)์—์„œ ์„œ๋ฒ„ ์ฝ”๋“œ๋ฅผ ์ง์ ‘ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๊ธฐ๋Šฅ

    • The resulting Server Functions can be passed to Client Components through props.
    • ๋ณ„๋„์˜ API ๋ผ์šฐํŠธ๋ฅผ ์ •์˜ํ•  ํ•„์š” ์—†์ด ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ๊ฐ„์˜ ๋ฐ์ดํ„ฐ ํ†ต์‹ ์„ ๊ฐ„์†Œํ™”
  • Because the underlying network calls are always asynchronous,ย 'use server'ย can only be used on async functions.

    • Promise๋ฅผ ๋ฆฌํ„ดํ•˜๋Š” ํ•จ์ˆ˜๋งŒ export ๊ฐ€๋Šฅ!
  • ๊ฐ„๊ฒฐํ•˜๊ณ  ๊ฐ„๋‹จํ•˜๊ณ  ํŽธํ•˜๊ณ  ์•ˆ์ „ํ•˜๊ฒŒ ์„œ๋ฒ„์—์„œ ์‹คํ–‰ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค์ˆ˜ ์žˆ์Œ!

    • ์„œ๋ฒ„์—์„œ๋งŒ ์‹คํ–‰๋˜๋Š” ์ฝ”๋“œ์ด๊ธฐ๋•Œ๋ฌธ์— ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” ํ˜ธ์ถœ๋งŒ ๊ฐ€๋Šฅ
      • โ‡’ ๋ณด์•ˆ์ƒ ๋ฌธ์ œ๊ฐ€ ๋˜๋Š” ์ฝ”๋“œ๋ฅผ ๋‹ค๋ฃจ๊ธฐ ์ข‹์Œ
  • HTML <form>

    • action ์†์„ฑ์— ์„œ๋ฒ„ ์•ก์…˜ ํ•จ์ˆ˜๋ฅผ ์ง€์ •ํ•˜๋ฉด, ํผ ์ œ์ถœ ์‹œ ์ž…๋ ฅ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์ž๋™์œผ๋กœ formData ๊ฐ์ฒด๋กœ ๊ตฌ์„ฑ๋จ
    • ํ•ด๋‹น ์„œ๋ฒ„ ์•ก์…˜ ํ•จ์ˆ˜์˜ ์ฒซ ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ „๋‹ฌ
    • ์ด ๊ฐ์ฒด์—์„œ get ๋ฉ”์„œ๋“œ๋กœ ํ•„์š”ํ•œ ๊ฐ’์„ ์ถ”์ถœ
  • revalidatePath(path)

    • ์ด ์˜ต์…˜์œผ๋กœ ์„œ๋ฒ„์•ก์…˜์œผ๋กœ ์–ป์€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ”๋กœ ํ™”๋ฉด์— ๋ Œ๋”๋ง ํ• ์ˆ˜ ์žˆ์Œ
    • ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์—์„œ๋งŒ ํ˜ธ์ถœ ๊ฐ€๋Šฅ
    • ํ•ด๋‹น ๊ฒฝ๋กœ์˜ cache๋œ ๋ฐ์ดํ„ฐ, full-route-cache๋Š” ๋ฌดํšจํ™”๋จ
      • ๋ฐ์ดํ„ฐ๊ฐ€ ์‚ญ์ œ๋˜๊ณ  ๋‹ค์‹œ ์ƒ์„ฑ๋˜์ง€๋Š” ์•Š์Œ
      • 1)์ƒˆ๋กœ๊ณ ์นจ ํ›„ 2)๋‹ค์‹œ ํ•ด๋‹น ํŽ˜์ด์ง€์— ์ ‘์†ํ•˜๋ฉด dynamic page์ฒ˜๋Ÿผ ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฅผ ๋ Œ๋”๋งํ•˜๊ณ , ํ•ด๋‹น ํŽ˜์ด์ง€๋ฅผ full-route-cache๋กœ ๋“ฑ๋ก
  • useActionState

    • ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์—์„œ Server Action์„ ์‚ฌ์šฉํ•  ๋•Œ, ํผ ์ œ์ถœ ์ƒํƒœ(๋กœ๋”ฉ ์ค‘ ์—ฌ๋ถ€ ๋“ฑ)๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ  ์ค‘๋ณต ์ œ์ถœ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ๊ถŒ์žฅ๋˜๋Š” React ํ›…

์˜๋ฌธ๊ฐ–๊ธฐ

Sever Action์— async await์„ ๋ถ™์ด์ง€ ์•Š์•„๋„ ์ž‘๋™ํ•œ๋‹ค?

Next Auth๋กœ SNS ๋กœ๊ทธ์ธ์„ ๊ตฌํ˜„ํ•˜๋Š”๋ฐ, login ์„œ๋ฒ„์•ก์…˜์„ ํ˜ธ์ถœํ• ๋•Œ await ์—†์ด๋„ ๋กœ๊ทธ์ธ์ด ๋˜๋Š”๊ฑธ ํ™•์ธ

// @/app/sign/sign.action.ts
"use server";
 
import { signIn, signOut } from "@/lib/auth";
 
type Provider = "google" | "github" | "naver" | "kakao";
 
export const login = async (provider: Provider, callback?: string) => {
  await signIn(provider, { redirectTo: callback || "/bookcase" });
};
 
export const logout = async () => {
  await signOut({ redirectTo: "/" });
};
// @/app/sign/google-login-button.tsx
'use client';
 
import { Button } from "@/components/ui/button";
import { login } from "./sign.action";
 
export function GoogleLoginButton() {
  return (
    <Button
      onClick={() => login("google")} /> // async - await ์—†์–ด๋„ ์‹คํ–‰ O!
      // ...
	)
	// ...
}

1. Server Action ์€,

  • Next.js App Router์—์„œ ํด๋ผ์ด์–ธํŠธ(Browser๋‚˜ Server Component)์—์„œ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋Š”, ์„œ๋ฒ„์—์„œ ์‹คํ–‰๋˜๋Š” ํ•จ์ˆ˜
    • use server ์„ ์–ธ์œผ๋กœ ํ‘œ์‹œ๋œ ํ•จ์ˆ˜
    • ํ•ญ์ƒ ์„œ๋ฒ„์—์„œ๋งŒ ์‹คํ–‰๋จ (hydration X)
    • ํ˜ธ์ถœํ•˜๋ฉด Promise๋ฅผ ๋ฐ˜ํ™˜ โ‡’ ๋ธŒ๋ผ์šฐ์ € - ์„œ๋ฒ„ ์‚ฌ์ด์˜ API ํ˜ธ์ถœ์„ Next.js๊ฐ€ ์ฒ˜๋ฆฌํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์— API route๋ฅผ ๋”ฐ๋กœ ๋งŒ๋“ค์ง€ ์•Š์•„๋„ OK

2. async / await๋Š” ์™œ ์“ฐ๋Š”๊ฑฐ์ง€?

JS์˜ single thread ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด!

  • ๋น„๋™๊ธฐ ์ž‘์—…(Promise, callback)์„ ๋™๊ธฐ์ ์œผ๋กœ ์‹คํ–‰ํ•˜๊ฒŒ ํ•ด์คŒ
    • await : Promise์˜ ์‹คํ–‰์ด ๋๋‚ ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ ธ๋‹ค๊ฐ€ ๊ฒฐ๊ณผ๋ฅผ ๋ฆฌํ„ด
    • async : ์ด๊ฒŒ ๋ถ™์€ ํ•จ์ˆ˜๋Š” ํ•ญ์ƒ Promise๋ฅผ ๋ฆฌํ„ดํ•˜๋„๋ก ํ•จ โ‡’ await ์‚ฌ์šฉ ๊ฐ€๋Šฅ

3. ๊ทธ๋ ‡๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ await ์—†์ด๋„ ์„œ๋ฒ„์•ก์…˜์ด ์‹คํ–‰๋ ๊นŒ?

  • ์œ„ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด, ๋ฒ„ํŠผ์˜ onClick ์ด๋ฒคํŠธ์— login ์„œ๋ฒ„์•ก์…˜์„ ํ˜ธ์ถœํ•˜๋Š” ํ™”์‚ดํ‘œ ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋œ๋‹ค
  • login ์„œ๋ฒ„์•ก์…˜์„ ํ˜ธ์ถœํ•˜๋ฉด Next Auth๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ Redirect๋ฅผ ์ผ์œผํ‚จ๋‹ค
    • await ์—ฌ๋ถ€์™€ ์ƒ๊ด€์—†์ด(๊ฒฐ๊ณผ๋ฅผ ๋ฆฌํ„ด๋ฐ›๋Š”์ง€์™€ ๊ด€๊ณ„์—†์ด) ํ•ด๋‹น ์„œ๋ฒ„์•ก์…˜์€ ํ˜ธ์ถœํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๊ณง๋ฐ”๋กœ ์ด๋™
  • ์œ ์ € ์ž…์žฅ์—์„œ๋Š” ํ™”๋ฉด์ด ์ „ํ™˜๋˜๊ธฐ๋•Œ๋ฌธ์— = ๋กœ๊ทธ์ธ ๋œ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๊ธฐ๋•Œ๋ฌธ์— ๋™์ผํ•œ ๋™์ž‘์„ ํ•˜๋Š”๊ฒƒ์ฒ˜๋Ÿผ ๋ณด๊ฒŒ ๋จ

4. ๊ทธ๋Ÿฌ๋ฉด ์–ธ์ œ await์„ ์‚ฌ์šฉํ• ๊นŒ?

  • server action์˜ ๊ฒฐ๊ณผ(๋ฆฌํ„ด)๊ฐ’์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜
  • ์‹คํ–‰ ์™„๋ฃŒ๋ฅผ ๋ณด์žฅํ•˜๊ณ  ์‹ถ์„๋•Œ
  • ๋งŒ์•ฝ ์•„๋ž˜์ฒ˜๋Ÿผ Promise ๋ฆฌํ„ด ๊ฐ’์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์ด๋ผ๋ฉด async / await์€ ๊ผญ ํ•„์š”ํ•จ!
// ๊ฒฐ๊ณผ๊ฐ’ ํ•„์š” O โ†’ await ํ•„์š”
onClick={async () => {
  const user = await login("google");
  console.log(user); // Promise ๋ฆฌํ„ด๊ฐ’์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ
}}

Server Action์—์„œ async/await ์‚ฌ์šฉ ๊ตฌ๋ถ„

์•ก์…˜ ์ข…๋ฅ˜์˜ˆ์‹œawait ํ•„์š” ์—ฌ๋ถ€์ด์œ 
๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๊ธฐ๋ฐ˜ ์•ก์…˜signIn(), signOut() (NextAuth)โŒ ์„ ํƒ์ ํ˜ธ์ถœ๋งŒ ํ•ด๋„ ๋ธŒ๋ผ์šฐ์ € ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๊ฐ€ ์ผ์–ด๋‚˜๋ฏ€๋กœ Promise ๊ฒฐ๊ณผ๋ฅผ ์•ˆ ์จ๋„ ๋จ
๋ฐ์ดํ„ฐ ๋ฐ˜ํ™˜ ์•ก์…˜DB ์กฐํšŒ, API ํ˜ธ์ถœ ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜โœ… ํ•„์š”๋ฐ˜ํ™˜๋œ ๋ฐ์ดํ„ฐ๋ฅผ UI๋‚˜ ๋กœ์ง์—์„œ ํ™œ์šฉํ•ด์•ผ ํ•˜๋ฏ€๋กœ ๋ฐ˜๋“œ์‹œ ๊ฒฐ๊ณผ ๋Œ€๊ธฐ ํ•„์š”
๋ถ€์ˆ˜ ํšจ๊ณผ(side effect)๋งŒ ์žˆ๋Š” ์•ก์…˜๋กœ๊ทธ ๊ธฐ๋ก, ํ†ต๊ณ„ ์ €์žฅโŒ ์„ ํƒ์ ๊ฒฐ๊ณผ๊ฐ’์ด ์ค‘์š”ํ•˜์ง€ ์•Š๊ณ  ์‹คํ–‰๋งŒ ๋ณด์žฅ๋˜๋ฉด ๋จ. ๋‹จ, ์‹คํ–‰ ์™„๋ฃŒ ํ™•์ธํ•˜๋ ค๋ฉด await ํ•„์š”
ํ›„์† ๋กœ์ง์ด ์žˆ๋Š” ์•ก์…˜๋กœ๊ทธ์ธ ํ›„ toast("์„ฑ๊ณต") ๋„์šฐ๊ธฐโœ… ํ•„์š”์•ก์…˜์ด ๋๋‚œ ๋’ค์—๋งŒ ์‹คํ–‰๋ผ์•ผ ํ•˜๋ฏ€๋กœ await ํ•„์š”
์—๋Ÿฌ ํ•ธ๋“ค๋ง์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐtry/catch๋กœ ์žก์•„์„œ ์ฒ˜๋ฆฌโœ… ํ•„์š”await์ด ์—†์œผ๋ฉด ์—๋Ÿฌ๊ฐ€ Promise์— ๋ฌถ์—ฌ์„œ catch๋˜์ง€ ์•Š์Œ