BlogFlow | 블로그

프론트엔드 개발과 IT 기술을 중심으로 실무 경험과 학습을 기록합니다.

프로그래밍

Next.js package.json 보는 법: scripts, dependencies, App Router까지

2026.02.12·수정 2026.03.20·약 19분

이 글에서 정리하는 내용

Next.js 프로젝트를 처음 열었을 때 많이 보게 되는 package.json, scripts, dependencies, devDependencies, next dev/build/start, App Router를 초심자 기준으로 한 흐름으로 정리합니다. 용어를 따로 외우기보다 프로젝트가 어떻게 실행되고, 어떤 파일이 어떤 역할을 하는지 연결해서 이해하는 데 초점을 맞춥니다.

Node.js, npm, package.json 먼저 이해하기

package.json과 실행 흐름을 설명하는 이미지

Next.js 프로젝트를 처음 열었을 때 가장 먼저 보게 되는 파일 중 하나가 package.json입니다. 초심자는 이 파일을 라이브러리 목록 정도로만 보기 쉽지만, 실제로는 프로젝트의 실행 규칙과 의존성 정보가 담긴 기준 파일에 가깝습니다. 저는 package.json을 볼 때마다 이 프로젝트가 어떤 도구 위에서 돌아가고, 어떤 명령으로 실행되며, 팀이 무엇을 공통 규칙으로 삼았는지를 먼저 읽습니다.

먼저 가장 바닥 개념부터 짚어보겠습니다. Node.js는 자바스크립트를 브라우저 밖에서도 실행할 수 있게 해주는 실행 환경입니다. npm은 패키지를 설치하고 관리하는 도구입니다. 그리고 package.json은 그 프로젝트가 어떤 패키지를 쓰는지, 어떤 명령으로 실행되는지를 적어두는 파일입니다. 주니어 개발자가 처음 헷갈리는 지점은 이 셋을 한 덩어리로 보는 것인데, 역할은 분명히 다릅니다. Node.js는 실행 환경, npm은 관리 도구, package.json은 설정과 선언의 기준 파일입니다.

먼저 알아두면 좋은 용어

런타임: 코드가 실제로 실행되는 환경
빌드: 배포 전에 코드를 묶고 최적화하는 과정
번들: 여러 파일을 실행 가능한 형태로 묶은 결과물
라우팅: URL에 따라 어떤 화면을 보여줄지 정하는 방식
의존성: 프로젝트가 동작하기 위해 필요한 외부 패키지

이 용어들은 뒤에서 계속 나옵니다. 특히 런타임과 빌드의 차이를 이해하면 dependencies와 devDependencies를 나누는 기준도 훨씬 쉽게 보입니다. 또 번들이라는 개념을 알고 있어야 어떤 패키지가 브라우저에 포함되는지, 어떤 코드는 서버에서만 실행되는지 구분할 수 있습니다.

package.json은 어떤 정보를 담고 있을까

{
  "name": "my-next-app",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "next": "15.3.3",
    "react": "19.1.0",
    "react-dom": "19.1.0"
  },
  "devDependencies": {
    "typescript": "5.8.3",
    "eslint": "9.25.0"
  }
}

위 예시는 아주 단순한 형태입니다. 여기서 name은 프로젝트 이름이고, scripts는 자주 쓰는 실행 명령을 묶어둔 영역입니다. dependencies는 앱이 돌아가는 데 필요한 패키지, devDependencies는 개발과 점검 과정에서 주로 쓰는 도구를 적는 곳입니다. 실무 프로젝트에서는 여기에 test, lint, seed, migrate 같은 명령과 추가 패키지가 더 붙습니다. 하지만 처음에는 모든 항목을 다 보려 하지 말고 scripts, dependencies, devDependencies 세 군데만 먼저 읽어도 충분합니다.

또 하나 자주 같이 보게 되는 파일이 package-lock.json입니다. package.json이 설치 기준을 적는 파일이라면, package-lock.json은 실제로 설치된 세부 버전 정보를 더 구체적으로 고정하는 역할을 합니다. 같은 저장소를 다른 사람이 받아도 비슷한 환경을 재현하기 쉽게 만드는 장치라고 이해하면 됩니다.

npm install은 실제로 무엇을 할까

npm install

주니어 개발자가 처음 많이 헷갈리는 부분이 바로 이 명령입니다. npm install은 package.json에 적힌 의존성 정보를 읽고, 조건에 맞는 패키지를 내려받아 설치합니다. 보통 이 과정에서 dependencies와 devDependencies가 함께 설치됩니다. 그래서 package.json을 수정한 뒤에는 이 명령으로 실제 개발 환경을 맞추게 됩니다. 즉 package.json은 선언이고, npm install은 그 선언을 실제 폴더 구조와 설치 결과로 옮기는 과정이라고 이해하면 훨씬 쉽습니다.

scripts와 next dev/build/start 이해하기

scripts는 반복해서 치는 터미널 명령을 표준화한 영역입니다. 처음에는 그냥 단축 명령처럼 보일 수 있지만, 팀 프로젝트에서는 이 부분이 굉장히 중요합니다. 누가 실행하든 같은 명령으로 같은 작업을 하게 만들기 때문입니다. 저는 scripts를 볼 때 이 프로젝트의 작업 흐름이 어떻게 설계되어 있는지 먼저 읽습니다. dev만 있는지, lint와 test가 있는지, 데이터 초기화용 seed 명령이 있는지에 따라 프로젝트 성격이 꽤 선명하게 보입니다.

가장 먼저 익혀야 하는 세 가지 명령

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  }
}

next dev는 개발용 서버를 띄우는 명령입니다. 코드를 수정하면 화면이 다시 반영되고, 에러도 개발용으로 자세히 보여줍니다. next build는 배포 전에 프로덕션용 결과물을 만드는 명령입니다. 코드를 최적화하고 필요한 산출물을 생성합니다. next start는 이렇게 만들어진 결과물을 실제 서비스에 가깝게 실행하는 명령입니다. 초심자가 자주 하는 착각은 dev만 알면 끝이라고 생각하는 것인데, dev는 개발용이고 start는 build가 끝난 결과물을 실행하는 단계라는 점이 다릅니다.

실행 흐름을 순서로 보면 더 쉽다

npm install
npm run dev
npm run build
npm run start

처음 프로젝트를 받으면 보통 npm install로 필요한 패키지를 설치합니다. 그다음 개발 중에는 npm run dev를 가장 많이 씁니다. 배포 전 점검이나 프로덕션 동작 확인이 필요할 때는 npm run build로 결과물을 만든 뒤 npm run start로 실행합니다. 여기서 install, dev, build, start가 각각 다른 역할이라는 흐름이 머릿속에 잡혀야 합니다.

주니어가 자주 헷갈리는 npm run과 npx

npm run build
npx next build

둘 다 결과적으로 next build를 실행할 수 있지만 의미는 다릅니다. npm run build는 package.json의 scripts에 정의된 build 명령을 실행합니다. 반면 npx next build는 설치된 next 실행 파일을 직접 호출하는 쪽에 가깝습니다. 팀 단위 프로젝트에서는 scripts로 명령을 통일해 두는 편이 좋습니다. 그래야 사람마다 제각각 다른 명령을 쓰지 않고, CI 환경에서도 같은 규칙을 사용할 수 있습니다.

lifecycle script도 한 번은 알고 가는 편이 좋다

{
  "scripts": {
    "prebuild": "echo checking",
    "build": "next build",
    "postbuild": "echo done"
  }
}

npm은 prebuild, build, postbuild처럼 이름 규칙이 맞는 스크립트를 이어서 실행할 수 있습니다. 이 개념은 초심자에게는 조금 낯설 수 있지만, 나중에 빌드 전 검사나 빌드 후 후처리를 자동화할 때 자주 보게 됩니다. 지금 단계에서는 scripts가 단순 목록이 아니라 작업 흐름을 구성할 수도 있다는 정도만 이해해도 충분합니다.

dependencies와 devDependencies 나누는 기준

dependencies와 devDependencies 차이를 설명하는 이미지

이 부분은 정말 많이 헷갈립니다. 그래서 저는 외우는 방식보다 질문 하나로 정리하는 편이 낫다고 봅니다. 이 패키지가 앱이 실제로 동작할 때 필요한가, 아니면 개발을 편하게 하거나 검사할 때 필요한가를 먼저 묻는 것입니다. 전자에 가까우면 dependencies, 후자에 가까우면 devDependencies로 보는 흐름이 가장 이해하기 쉽습니다.

가장 쉬운 구분 기준

구분 주로 쓰이는 시점
dependencies 앱 실행과 화면 동작에 필요할 때
devDependencies 개발, 빌드, 테스트, 검사 도구일 때

예를 들어 next, react, react-dom은 앱 자체를 실행하는 데 필요하므로 dependencies에 들어갑니다. 반면 typescript, eslint, jest 같은 도구는 개발과 검사에 더 가깝기 때문에 devDependencies에 들어가는 경우가 많습니다. 물론 프로젝트 구조에 따라 예외처럼 보이는 경우도 있지만, 초심자 단계에서는 이 기준이 가장 실용적입니다.

예시로 보면 훨씬 빠르게 익힌다

{
  "dependencies": {
    "next": "15.3.3",
    "react": "19.1.0",
    "react-dom": "19.1.0",
    "@tanstack/react-query": "5.75.2"
  },
  "devDependencies": {
    "typescript": "5.8.3",
    "eslint": "9.25.0",
    "jest": "29.7.0"
  }
}

TanStack Query처럼 화면에서 서버 데이터를 다루는 데 실제로 쓰이는 라이브러리는 dependencies에 두는 것이 자연스럽습니다. 반면 TypeScript는 개발 중 타입 점검을 돕는 도구이고, ESLint는 코드 스타일과 문제를 검사하는 도구이며, Jest는 테스트를 위한 도구이므로 devDependencies에 두는 편이 일반적입니다. 주니어 개발자가 여기서 놓치기 쉬운 부분은 “개발에 쓰였으니 전부 devDependencies 아닐까”라고 생각하는 것인데, 실제 런타임에 필요한 라이브러리는 개발 중에도 당연히 쓰이지만 분류는 런타임 기준으로 봐야 합니다.

브라우저 번들에 포함되는지는 다른 문제다

'use client'

import { useQuery } from '@tanstack/react-query'

export default function ProductList() {
  const { data } = useQuery({ queryKey: ['products'] })
  return <div>{data?.length}</div>
}

어떤 패키지가 브라우저 번들에 포함되는지는 dependencies냐 devDependencies냐만으로 결정되지 않습니다. 실제로 어디에서 import했는지, 클라이언트 컴포넌트인지 서버 컴포넌트인지, 번들링 과정에서 어떻게 처리되는지가 함께 영향을 줍니다. 이 문장은 주니어 개발자가 꼭 알아야 합니다. 패키지 분류와 브라우저 포함 여부는 연결은 되어 있지만 완전히 같은 문제는 아닙니다.

App Router와 프로젝트 구조 읽는 법

Next.js를 공부하다 보면 App Router라는 말을 많이 보게 됩니다. 이 개념은 단순히 라우터 이름만 바뀐 것이 아니라, 프로젝트 구조를 읽는 방식에도 영향을 줍니다. 특히 app 폴더, layout, page, 서버 컴포넌트, 클라이언트 컴포넌트 같은 개념이 함께 나와서 초심자가 갑자기 어렵게 느끼기 쉽습니다. 그래서 저는 App Router를 볼 때 파일 구조부터 먼저 보고, 그 다음 렌더링 방식을 보는 순서로 이해하는 편이 낫다고 생각합니다.

가장 기본적인 App Router 구조

app/
  layout.tsx
  page.tsx
  products/
    page.tsx
    [id]/
      page.tsx

App Router는 파일 시스템 기반 라우팅을 사용합니다. 쉽게 말해 폴더와 파일 이름이 URL 구조와 연결됩니다. app/page.tsx는 루트 페이지를 뜻하고, app/products/page.tsx는 /products 경로를 뜻합니다. app/products/[id]/page.tsx처럼 대괄호가 들어가면 동적 라우트가 됩니다. 예를 들어 /products/1, /products/2 같은 URL을 처리할 수 있습니다. 처음에는 규칙이 많아 보이지만, 폴더 구조가 곧 URL 구조라는 감각만 익히면 훨씬 덜 어렵습니다.

route segment와 colocating도 같이 알아두자

app/
  products/
    page.tsx
    ui/
      ProductCard.tsx
    lib/
      formatPrice.ts

여기서 products 같은 폴더 하나를 route segment라고 부릅니다. 그리고 App Router에서는 라우트 파일 옆에 관련 컴포넌트나 유틸 파일을 함께 두는 colocating 패턴도 자주 씁니다. 예를 들어 ui 폴더나 lib 폴더를 app 안에 같이 두어도, 실제로 공개 라우트가 되는 것은 page 파일처럼 라우팅 규칙을 가진 파일입니다. 이 개념을 알면 app 폴더 안에 여러 파일이 보여도 “이게 다 URL이 되는 건 아니구나”라는 감각이 생깁니다.

layout과 page는 역할이 다르다

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ko">
      <body>{children}</body>
    </html>
  )
}

layout은 공통 골격을 잡는 파일입니다. 헤더, 푸터, 공통 레이아웃처럼 여러 페이지가 함께 쓰는 구조를 담당할 수 있습니다. page는 실제 화면 단위의 진입점에 가깝습니다. 초심자가 자주 하는 착각은 둘 다 그냥 페이지 파일이라고 보는 것인데, layout은 감싸는 구조, page는 해당 경로의 실제 콘텐츠라는 차이가 있습니다.

서버 컴포넌트와 클라이언트 컴포넌트도 같이 보자

'use client'

import { useState } from 'react'

export default function Counter() {
  const [count, setCount] = useState(0)

  return (
    <button onClick={() => setCount(count + 1)}>
      {count}
    </button>
  )
}

App Router에서는 기본적으로 서버 컴포넌트 개념이 함께 등장합니다. Next.js의 App Router 문서 흐름을 따라가면 라우트는 기본적으로 서버 쪽에서 렌더링되는 방향으로 설명되고, 상호작용이 필요한 부분만 클라이언트로 넘기는 식으로 이해하는 편이 자연스럽습니다. 그리고 상태 훅이나 클릭 이벤트처럼 브라우저 상호작용이 필요한 경우에는 ‘use client’ 지시어를 붙여 클라이언트 컴포넌트로 명시하게 됩니다. 주니어 개발자가 이 부분에서 가장 많이 막히는 이유는 “왜 어떤 파일은 그냥 되고, 어떤 파일은 ‘use client’가 필요하지?”라는 질문 때문입니다. 기준은 브라우저 전용 기능을 쓰는지 여부로 보면 됩니다. useState, useEffect, 이벤트 핸들러처럼 사용자 상호작용이 필요한 경우 클라이언트 컴포넌트가 필요합니다. React 문서 기준으로 보면 ‘use client’는 서버와 클라이언트 모듈 경계를 만드는 지시어에 가깝습니다.

실무 프로젝트를 처음 읽을 때 추천하는 순서

1. package.json의 scripts 확인
2. dependencies와 devDependencies 확인
3. app 폴더 구조 확인
4. layout.tsx, page.tsx부터 읽기
5. 상태 관리와 데이터 패칭 라이브러리 확인

이 순서로 보면 훨씬 덜 헤맵니다. 먼저 scripts를 보면 프로젝트를 어떻게 실행해야 하는지 알 수 있고, dependencies를 보면 어떤 스택을 쓰는지 보입니다. 그다음 app 폴더를 보면 라우팅 구조가 보이고, layout과 page를 보면 화면의 뼈대와 진입점이 정리됩니다. 마지막으로 TanStack Query, Zustand, Redux Toolkit 같은 상태 관리 도구나 axios 같은 통신 도구를 보면 데이터 흐름을 읽을 수 있습니다. 실무 프로젝트를 처음 열었을 때 모든 파일을 위에서 아래로 다 읽으려 하면 오히려 구조가 안 잡히기 쉽습니다.

정리

package.json은 단순한 설정 파일이 아니라 프로젝트의 실행 기준을 보여주는 출발점입니다. scripts를 보면 어떤 작업 흐름으로 개발하는지 보이고, dependencies와 devDependencies를 보면 어떤 것이 실제 실행에 필요하고 어떤 것이 개발 도구인지 구분할 수 있습니다. 여기에 Next.js의 next dev, next build, next start 흐름과 App Router의 폴더 구조까지 연결해서 보면, 처음에는 낯설었던 프로젝트도 훨씬 덜 어렵게 읽히기 시작합니다. 초심자 단계에서는 모든 세부 규칙을 한 번에 외우려 하기보다, 실행 흐름과 폴더 구조를 연결해서 보는 연습을 하는 편이 훨씬 효율적입니다.

많이 받는 질문

Q. package.json은 처음부터 전부 외워야 하나요?
그럴 필요는 없습니다. 처음에는 scripts, dependencies, devDependencies만 확실히 읽을 수 있어도 충분합니다.

Q. next dev와 next start는 결국 둘 다 실행 명령 아닌가요?
둘 다 실행 명령은 맞지만 목적이 다릅니다. dev는 개발용 서버이고, start는 build 결과물을 기준으로 한 프로덕션 실행에 가깝습니다.

Q. App Router에서 무조건 ‘use client’를 붙여야 하나요?
아닙니다. 상태 훅, 브라우저 이벤트, DOM 접근처럼 클라이언트 기능이 필요한 경우에만 붙입니다. 기본은 서버 컴포넌트 흐름으로 이해하는 편이 좋습니다.

이 글이 마음에 드세요?

RSS 피드를 구독하세요!

댓글 남기기