title: use server (서버에서 실행하기)
description: 서버에서 코드를 실행하기 위해 use server 지시어(directive)를 사용하는 방법에 대해 배워봅니다.
url: "https://nextjs.org/docs/app/api-reference/directives/use-server"
version: 16.1.6
lastUpdated: 2026-02-27
prerequisites:
안녕하세요 여러분! 오늘 함께 공부할 내용은 Next.js 개발을 하면서 정말 자주 만나게 될 핵심 기능, 바로 use server 지시어(directive)입니다.
use server 지시어는 특정 함수나 파일 전체가 반드시 서버 측(server-side)에서 실행되어야 함을 지정해주는 역할을 해요. 데이터베이스에 직접 접근하거나 비밀키를 다루는 등 브라우저(클라이언트)에 노출되면 안 되는 안전한 작업이 필요할 때 아주 유용하죠.
이 지시어는 파일의 맨 위에 작성해서 "이 파일 안의 모든 함수는 서버에서 실행된다!"라고 선언할 수도 있고, 특정 함수 안의 맨 위에 인라인(inline)으로 작성해서 그 함수만 서버 함수(Server Function)로 지정할 수도 있습니다. 참고로 이 기능은 Next.js만의 독자적인 기능이 아니라, React의 최신 공식 기능 중 하나랍니다.
use server 사용하기다음 예제를 함께 볼까요? 파일의 맨 윗부분에 use server 지시어가 작성되어 있습니다. 이렇게 선언하면, 이 파일 안에 작성된 모든 함수들은 오직 서버에서만 실행됩니다. 클라이언트(브라우저)로는 코드가 전송되지 않아요.
'use server'
import { db } from '@/lib/db' // Your database client
export async function createUser(data: { name: string; email: string }) {
const user = await db.user.create({ data })
return user
}
'use server'
import { db } from '@/lib/db' // Your database client
export async function createUser(data) {
const user = await db.user.create({ data })
return user
}
💡 강사의 팁: > 실무에서는 이렇게 서버에서만 동작하는 함수들(주로 데이터베이스를 조작하거나 API 통신을 하는 로직)을 모아두는 파일을
actions.ts혹은server-actions.ts와 같이 별도의 파일로 분리해서 관리하는 패턴을 아주 많이 씁니다. 코드가 섞이지 않아서 나중에 유지보수하기가 훨씬 편해지거든요!
버튼을 클릭하거나 폼을 제출하는 등 사용자와의 상호작용이 필요한 클라이언트 컴포넌트에서도 당연히 서버 함수를 호출해야 할 때가 생깁니다.
클라이언트 컴포넌트에서 서버 함수를 사용하려면, 반드시 파일 맨 위에 use server 지시어가 있는 전용 파일에 서버 함수를 먼저 만들어야 해요. 그런 다음, 이 서버 함수들을 클라이언트 컴포넌트(혹은 다른 서버 컴포넌트)로 가져와서(import) 실행할 수 있습니다.
예를 들어, actions.ts 파일에 유저 목록을 불러오는 fetchUsers라는 서버 함수가 있다고 가정해 보겠습니다.
'use server'
import { db } from '@/lib/db' // Your database client
export async function fetchUsers() {
const users = await db.user.findMany()
return users
}
'use server'
import { db } from '@/lib/db' // Your database client
export async function fetchUsers() {
const users = await db.user.findMany()
return users
}
이제 이렇게 만든 fetchUsers 서버 함수를 클라이언트 컴포넌트로 불러와서(import), 클라이언트 측에서 버튼을 누를 때 실행되도록 연결할 수 있어요.
'use client'
import { fetchUsers } from '../actions'
export default function MyButton() {
return <button onClick={() => fetchUsers()}>Fetch Users</button>
}
'use client'
import { fetchUsers } from '../actions'
export default function MyButton() {
return <button onClick={() => fetchUsers()}>Fetch Users</button>
}
💡 강사의 팁: > "왜 그냥 클라이언트 컴포넌트 안에
use server함수를 직접 만들면 안 되나요?"라고 궁금해하시는 분들이 꼭 계십니다.
클라이언트 컴포넌트 파일 안에async function을 선언하고 그 안에use server를 쓰면 에러가 납니다! 클라이언트 파일은 번들링되어 브라우저로 넘어가는 코드이기 때문에 그 안에서 서버 전용 코드를 선언할 수 없어요. 그래서 항상 분리된 파일(actions.ts등)에서 만들어두고import해서 써야 한다는 점! 꼭 기억해주세요.
use server 사용하기파일 전체가 아니라 특정 함수 하나만 서버 함수로 만들고 싶다면 어떻게 할까요? 다음 예제처럼 함수의 맨 앞부분에 인라인으로 use server를 작성해주면 됩니다. 이렇게 하면 해당 함수만 서버 함수(Server Function)로 지정됩니다.
import { EditPost } from './edit-post'
import { revalidatePath } from 'next/cache'
export default async function PostPage({ params }: { params: { id: string } }) {
const post = await getPost(params.id)
async function updatePost(formData: FormData) {
'use server'
await savePost(params.id, formData)
revalidatePath(`/posts/${params.id}`)
}
return <EditPost updatePostAction={updatePost} post={post} />
}
import { EditPost } from './edit-post'
import { revalidatePath } from 'next/cache'
export default async function PostPage({ params }) {
const post = await getPost(params.id)
async function updatePost(formData) {
'use server'
await savePost(params.id, formData)
revalidatePath(`/posts/${params.id}`)
}
return <EditPost updatePostAction={updatePost} post={post} />
}
💡 강사의 팁: > 이 방식은 위 예제처럼 서버 컴포넌트 안에서 게시글 수정 등 가벼운 폼(form) 데이터를 처리하는 액션 함수를 빠르게 정의해서 자식 컴포넌트에게 넘겨줄 때(props) 아주 편리합니다.
파일 이동 없이 한눈에 코드가 들어오니까요. 하지만 코드가 길어지면 파일이 복잡해질 수 있으니, 로직이 무거워지면 결국 별도 파일로 분리하는 걸 추천합니다.
use server 지시어를 사용할 때 정말 귀에 못이 박히도록 강조하고 싶은 내용입니다. 서버 측에서 동작하는 모든 로직은 철저하게 안전해야 하며, 민감한 데이터는 반드시 보호되어야 합니다.
서버 함수는 마치 외부로 열려있는 API 엔드포인트(API Endpoint)와 똑같다고 생각하셔야 합니다. 클라이언트가 원할 때 언제든 이 함수를 호출할 수 있으니까요.
데이터베이스의 내용을 바꾸거나 중요한 정보를 다루는 민감한 서버 작업을 수행하기 전에는, 항상 유저가 로그인한 사람인지(인증) 확인하고, 해당 작업을 할 자격과 권한이 있는지(인가) 검증해야 합니다.
'use server'
import { db } from '@/lib/db' // Your database client
import { authenticate } from '@/lib/auth' // Your authentication library
export async function createUser(
data: { name: string; email: string },
token: string
) {
const user = authenticate(token)
if (!user) {
throw new Error('Unauthorized')
}
const newUser = await db.user.create({ data })
return newUser
}
'use server'
import { db } from '@/lib/db' // Your database client
import { authenticate } from '@/lib/auth' // Your authentication library
export async function createUser(data, token) {
const user = authenticate(token)
if (!user) {
throw new Error('Unauthorized')
}
const newUser = await db.user.create({ data })
return newUser
}
💡 강사의 팁: > "프론트엔드단에서 이미 숨김 처리해 둔 버튼에서만 호출되는 거니까 안전하겠지?" 라고 생각하시면 절대 안 됩니다! 악의적인 사용자는 개발자 도구를 열어 언제든지 서버 함수에 직접 이상한 데이터를 실어서 요청을 보낼 수 있어요.
그래서 서버 함수 최상단에서는 위의 예제처럼 토큰이나 세션을 검사하는 로직과 함께, 클라이언트에서 넘어온 데이터(파라미터)가 올바른 형태인지 검증하는 로직(Validation)이 필수적으로 들어가야 합니다.Zod같은 스키마 검증 라이브러리를 적극 활용하시길 강력히 권장합니다!
use server 기능에 대한 더 깊고 자세한 원리가 궁금하시다면, React 공식 문서의 use server 페이지를 꼭 한 번 읽어보시길 바랍니다!