react-vite-to-next-migration

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Vite React → Next.js App Router 마이그레이션

Vite React → Next.js App Router 迁移

Vite + React(TS) 프로젝트를 Next.js(App Router, v16) 프로젝트로 변환하는 스킬. 기존 코드의 동작을 유지하면서 Next.js 구조로 옮기는 것이 목표.
本技能用于将Vite + React(TS)项目转换为Next.js(App Router, v16)项目,目标是在保留原有代码功能的前提下迁移到Next.js架构。

동작 흐름

执行流程

1단계: 프로젝트 상태 감지

第1步:项目状态检测

다음 파일들을 확인하여 마이그레이션 가능 여부와 옮길 대상을 판별한다:
확인 대상감지 방법
Vite 프로젝트
vite.config.ts
또는
vite.config.js
존재
TypeScript
tsconfig.json
또는
tsconfig.app.json
존재
React Router
package.json
의 dependencies에
react-router
또는
react-router-dom
존재
TanStack Query
package.json
의 dependencies에
@tanstack/react-query
존재
Zustand
package.json
의 dependencies에
zustand
존재
Tailwind CSS
package.json
의 dependencies/devDependencies에
tailwindcss
존재
환경 변수
.env*
파일에
VITE_
접두사 변수 존재
정적 자산
public/
디렉토리 존재
진입점
index.html
,
src/main.tsx
,
src/App.tsx
존재
패키지 매니저
pnpm-lock.yaml
→ pnpm,
yarn.lock
→ yarn,
bun.lockb
/
bun.lock
→ bun, 그 외 → npm
vite.config.*
이 없다면 마이그레이션 대상이 아니므로 사용자에게 알리고 중단한다.
通过检查以下文件判断是否支持迁移以及需要迁移的内容:
检测对象检测方式
Vite项目存在
vite.config.ts
vite.config.js
TypeScript存在
tsconfig.json
tsconfig.app.json
React Router
package.json
的dependencies中包含
react-router
react-router-dom
TanStack Query
package.json
的dependencies中包含
@tanstack/react-query
Zustand
package.json
的dependencies中包含
zustand
Tailwind CSS
package.json
的dependencies/devDependencies中包含
tailwindcss
环境变量
.env*
文件中存在带
VITE_
前缀的变量
静态资源存在
public/
目录
入口点存在
index.html
src/main.tsx
src/App.tsx
包管理器
pnpm-lock.yaml
→ pnpm,
yarn.lock
→ yarn,
bun.lockb
/
bun.lock
→ bun,其他 → npm
如果不存在
vite.config.*
文件则不属于迁移对象,告知用户后终止操作。

2단계: 마이그레이션 개요 안내

第2步:迁移概览说明

작업 시작 전 다음을 사용자에게 안내한다:
  • App Router(
    src/app
    ) 구조로 변환됨
  • React Router 라우트는 App Router의 디렉토리 기반 라우트로 변환됨
  • VITE_
    환경 변수는
    NEXT_PUBLIC_
    접두사로 변경됨
  • 브라우저 API/이벤트 훅을 사용하는 컴포넌트는
    'use client'
    가 추가됨
  • Vite 전용 파일(
    index.html
    ,
    vite.config.*
    ,
    src/main.tsx
    ,
    src/vite-env.d.ts
    )은 제거됨
  • 기존 컴포넌트/스타일/유틸 코드는 최대한 그대로 보존됨
在开始操作前向用户告知以下内容:
  • 将转换为App Router(
    src/app
    )架构
  • React Router路由将转换为App Router的目录式路由
  • VITE_
    开头的环境变量将修改为
    NEXT_PUBLIC_
    前缀
  • 使用浏览器API/事件Hook的组件会自动添加
    'use client'
    标识
  • Vite专属文件(
    index.html
    vite.config.*
    src/main.tsx
    src/vite-env.d.ts
    )将被移除
  • 原有组件/样式/工具代码将尽可能完整保留

3단계: 의존성 변경

第3步:依赖调整

Vite 관련 패키지 제거, Next.js 패키지 추가:
bash
{pm} remove vite @vitejs/plugin-react @vitejs/plugin-react-swc vite-tsconfig-paths @tailwindcss/vite react-router react-router-dom
{pm} install next@latest react@latest react-dom@latest
{pm} install -D @types/node
{pm}
은 1단계에서 감지한 패키지 매니저로 대체한다. 존재하지 않는 패키지는
remove
대상에서 제외한다.
package.json
scripts
를 Next.js 기준으로 교체:
json
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  }
}
移除Vite相关依赖包,添加Next.js依赖包:
bash
{pm} remove vite @vitejs/plugin-react @vitejs/plugin-react-swc vite-tsconfig-paths @tailwindcss/vite react-router react-router-dom
{pm} install next@latest react@latest react-dom@latest
{pm} install -D @types/node
{pm}
替换为第1步检测到的包管理器,不存在的包将从
remove
列表中排除。
package.json
中的
scripts
替换为Next.js标准配置:
json
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  }
}

4단계: 설정 파일 교체

第4步:配置文件替换

다음 파일을 제거한다:
  • vite.config.ts
    /
    vite.config.js
  • index.html
  • src/vite-env.d.ts
next.config.ts
를 새로 생성:
ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  reactStrictMode: true
}

export default nextConfig
tsconfig.json
을 Next.js용으로 갱신 (기존
paths
는 유지):
json
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [{ "name": "next" }],
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}
기존
tsconfig.app.json
,
tsconfig.node.json
은 삭제한다.
移除以下文件:
  • vite.config.ts
    /
    vite.config.js
  • index.html
  • src/vite-env.d.ts
新建
next.config.ts
ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  reactStrictMode: true
}

export default nextConfig
更新
tsconfig.json
为Next.js适配版本(保留原有
paths
配置):
json
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [{ "name": "next" }],
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}
原有
tsconfig.app.json
tsconfig.node.json
将被删除。

5단계: 진입점 변환 (App Router 구조 생성)

第5步:入口点转换(生成App Router架构)

src/app/layout.tsx
를 생성하고, 기존
index.html
<head>
정보(타이틀, lang, 메타)를 옮긴다:
tsx
import type { Metadata } from 'next'
import './globals.css'

export const metadata: Metadata = {
  title: 'App',
  description: ''
}

export default function RootLayout({
  children
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <html lang="ko">
      <body>{children}</body>
    </html>
  )
}
기존
src/index.css
src/app/globals.css
로 이동. Tailwind 사용 프로젝트라면 상단에 다음 지시문이 유지되어야 한다:
css
@import 'tailwindcss';
src/main.tsx
,
src/App.tsx
,
src/App.css
는 라우팅 변환(6단계) 후 제거한다.
新建
src/app/layout.tsx
,将原有
index.html
中的
<head>
信息(标题、语言、元数据)迁移过来:
tsx
import type { Metadata } from 'next'
import './globals.css'

export const metadata: Metadata = {
  title: 'App',
  description: ''
}

export default function RootLayout({
  children
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <html lang="ko">
      <body>{children}</body>
    </html>
  )
}
将原有
src/index.css
移动到
src/app/globals.css
。如果是使用Tailwind的项目,需要保留文件顶部的导入指令:
css
@import 'tailwindcss';
src/main.tsx
src/App.tsx
src/App.css
将在路由转换(第6步)完成后移除。

6단계: 라우팅 변환 (React Router → App Router)

第6步:路由转换(React Router → App Router)

기존 React Router 라우트 정의(
createBrowserRouter
/
<Routes>
)를 분석한 뒤, 각 경로를 App Router 디렉토리 구조로 매핑한다.
React RouterApp Router
path: '/'
<Home />
src/app/page.tsx
path: '/about'
<About />
src/app/about/page.tsx
path: '/posts/:id'
<Post />
src/app/posts/[id]/page.tsx
path: '*'
<NotFound />
src/app/not-found.tsx
공통 레이아웃 컴포넌트
src/app/layout.tsx
또는 하위
layout.tsx
각 페이지 컴포넌트는 다음 규칙으로 옮긴다:
  • 기존 컴포넌트 본문은 최대한 그대로 유지
  • useNavigate
    ,
    useLocation
    ,
    useParams
    ,
    <Link>
    등 React Router API 사용 지점은 다음과 같이 치환:
    • useNavigate()
      useRouter()
      (
      next/navigation
      )
    • useParams()
      → 페이지 컴포넌트의
      params
      prop (
      async
      페이지에서는
      await params
      )
    • useLocation().pathname
      usePathname()
      (
      next/navigation
      )
    • <Link to="...">
      <Link href="...">
      (
      next/link
      )
  • 위 훅 중 하나라도 사용하거나,
    useState
    /
    useEffect
    /이벤트 핸들러/브라우저 API를 사용하는 페이지는 파일 최상단에
    'use client'
    를 추가한다.
  • 그렇지 않은 정적 페이지는 서버 컴포넌트(기본값)로 둔다.
작업이 끝나면
src/main.tsx
,
src/App.tsx
,
src/App.css
, 기존 라우터 정의 파일(
src/routes/index.tsx
등 라우팅 정의에만 쓰인 파일)을 제거한다. 단, 페이지 컴포넌트 본체는
src/app/**/page.tsx
로 옮겨지므로 보존된다.
解析原有React Router的路由定义(
createBrowserRouter
/
<Routes>
),将每个路径映射为App Router的目录结构:
React RouterApp Router
path: '/'
<Home />
src/app/page.tsx
path: '/about'
<About />
src/app/about/page.tsx
path: '/posts/:id'
<Post />
src/app/posts/[id]/page.tsx
path: '*'
<NotFound />
src/app/not-found.tsx
公共布局组件
src/app/layout.tsx
或子目录下的
layout.tsx
每个页面组件按照以下规则迁移:
  • 原有组件内容尽可能完整保留
  • 所有使用React Router API的位置做如下替换:
    • useNavigate()
      useRouter()
      (来自
      next/navigation
    • useParams()
      → 页面组件的
      params
      属性(异步页面中需使用
      await params
    • useLocation().pathname
      usePathname()
      (来自
      next/navigation
    • <Link to="...">
      <Link href="...">
      (来自
      next/link
  • 如果页面使用了上述任意Hook,或者使用了
    useState
    /
    useEffect
    /事件处理函数/浏览器API,需要在文件顶部添加
    'use client'
    标识
  • 其余静态页面默认保留为服务端组件
操作完成后移除
src/main.tsx
src/App.tsx
src/App.css
以及原有路由定义文件(如仅用于路由配置的
src/routes/index.tsx
等),页面组件本身会被迁移到
src/app/**/page.tsx
保留。

7단계: 환경 변수 변환

第7步:环境变量转换

.env
,
.env.local
,
.env.development
,
.env.production
등 모든
.env*
파일에서:
  • VITE_FOO=bar
    NEXT_PUBLIC_FOO=bar
소스 코드 전반에서:
  • import.meta.env.VITE_FOO
    process.env.NEXT_PUBLIC_FOO
  • import.meta.env.MODE
    process.env.NODE_ENV
  • import.meta.env.DEV
    process.env.NODE_ENV !== 'production'
  • import.meta.env.PROD
    process.env.NODE_ENV === 'production'
  • import.meta.env.BASE_URL
    → 사용처 검토 후 제거 또는
    next.config.ts
    basePath
    로 대체
在所有
.env*
文件(
.env
.env.local
.env.development
.env.production
等)中做如下替换:
  • VITE_FOO=bar
    NEXT_PUBLIC_FOO=bar
在所有源码中做如下替换:
  • import.meta.env.VITE_FOO
    process.env.NEXT_PUBLIC_FOO
  • import.meta.env.MODE
    process.env.NODE_ENV
  • import.meta.env.DEV
    process.env.NODE_ENV !== 'production'
  • import.meta.env.PROD
    process.env.NODE_ENV === 'production'
  • import.meta.env.BASE_URL
    → 评估使用场景后删除,或替换为
    next.config.ts
    中的
    basePath
    配置

8단계: 정적 자산 처리

第8步:静态资源处理

  • public/
    디렉토리는 그대로 사용 가능. 별도 이동 불필요.
  • 단, Vite에서
    /vite.svg
    같이 루트 절대경로로 참조했던 기본 자산이 더 이상 필요 없다면 제거한다.
  • 소스 코드 내부에서
    import logo from './assets/logo.svg'
    같은 임포트 형태는 그대로 동작하지만,
    public/
    자산을
    next/image
    로 사용하는 형태(
    <Image src="/logo.svg" ... />
    )도 안내한다.
  • public/
    目录可直接复用,无需额外移动
  • 如果存在Vite默认生成的根路径资源如
    /vite.svg
    且不再需要可以删除
  • 源码中
    import logo from './assets/logo.svg'
    的导入方式可正常使用,同时可引导用户使用
    next/image
    加载
    public/
    资源的方式(
    <Image src="/logo.svg" ... />

9단계: Tailwind CSS (사용 중인 경우)

第9步:Tailwind CSS适配(如果项目使用)

기존
@tailwindcss/vite
플러그인은 3단계에서 이미 제거되었다. Next.js에서는 PostCSS 기반 설정으로 전환한다.
bash
{pm} install -D tailwindcss @tailwindcss/postcss postcss
프로젝트 루트에
postcss.config.mjs
생성:
js
export default {
  plugins: {
    '@tailwindcss/postcss': {}
  }
}
src/app/globals.css
상단의
@import 'tailwindcss';
는 그대로 유지된다.
原有
@tailwindcss/vite
插件已在第3步移除,Next.js中切换为PostCSS基础配置:
bash
{pm} install -D tailwindcss @tailwindcss/postcss postcss
在项目根目录新建
postcss.config.mjs
js
export default {
  plugins: {
    '@tailwindcss/postcss': {}
  }
}
src/app/globals.css
顶部的
@import 'tailwindcss';
指令保持不变。

10단계: TanStack Query (사용 중인 경우)

第10步:TanStack Query适配(如果项目使用)

기존
QueryClientProvider
래핑 코드는 Next.js App Router 구조에 맞게 별도 Provider 컴포넌트로 분리한다. 자세한 구성은
react-next-scaffold
스킬의 TanStack Query 단계와 동일하게 처리한다.
요약:
  1. {pm} install @tanstack/react-query-next-experimental
    추가 설치
  2. src/providers/query.tsx
    생성 (
    'use client'
    +
    QueryClientProvider
    +
    ReactQueryStreamedHydration
    )
  3. src/app/layout.tsx
    <body>
    안쪽
    children
    <QueryProvider>
    로 래핑
原有
QueryClientProvider
封装代码需要按照Next.js App Router架构拆分到独立的Provider组件中,具体配置和
react-next-scaffold
技能的TanStack Query步骤一致。
摘要:
  1. 新增安装依赖:
    {pm} install @tanstack/react-query-next-experimental
  2. 新建
    src/providers/query.tsx
    (包含
    'use client'
    +
    QueryClientProvider
    +
    ReactQueryStreamedHydration
  3. src/app/layout.tsx
    <body>
    标签内用
    <QueryProvider>
    包裹
    children

11단계: Zustand (사용 중인 경우)

第11步:Zustand适配(如果项目使用)

기존 store 파일은 그대로 사용 가능하다. 단, store를 사용하는 컴포넌트는 클라이언트 컴포넌트여야 하므로 해당 파일 상단에
'use client'
를 보장한다.
原有store文件可直接使用,需要确保使用store的组件是客户端组件,在对应文件顶部添加
'use client'
标识。

12단계: 정리 및 검증

第12步:清理与验证

다음 작업을 수행한다:
  1. node_modules
    및 lock 파일 재생성:
    bash
    rm -rf node_modules
    {pm} install
  2. .gitignore
    .next/
    ,
    next-env.d.ts
    추가 (없는 경우)
  3. 빌드 검증:
    bash
    {pm} run build
  4. 빌드 에러가 있을 경우, 에러 메시지에 따라 다음을 우선 점검:
    • 서버 컴포넌트에서 브라우저 전용 API 사용 → 해당 컴포넌트에
      'use client'
      추가
    • import.meta.env
      잔존 → 7단계 규칙으로 치환
    • React Router API 잔존 → 6단계 규칙으로 치환
执行以下操作:
  1. 重新生成
    node_modules
    和lock文件:
    bash
    rm -rf node_modules
    {pm} install
  2. 如果
    .gitignore
    中不存在
    .next/
    next-env.d.ts
    则添加
  3. 构建验证:
    bash
    {pm} run build
  4. 如果出现构建错误,优先按照以下规则排查:
    • 服务端组件使用了浏览器专属API → 为对应组件添加
      'use client'
      标识
    • 存在残留的
      import.meta.env
      → 按照第7步规则替换
    • 存在残留的React Router API → 按照第6步规则替换

주의사항

注意事项

  • 라우팅 구조와 페이지별 클라이언트/서버 컴포넌트 결정은 자동으로 판별하되, 모호한 경우 안전을 위해
    'use client'
    를 추가한다.
  • 기존 컴포넌트 코드는 라우팅/환경변수/임포트 경로 외에는 수정하지 않는다.
  • 패키지 매니저는 기존 프로젝트의 lock 파일로 판별한다:
    • pnpm-lock.yaml
      pnpm
    • yarn.lock
      yarn
    • bun.lockb
      또는
      bun.lock
      bun
    • package-lock.json
      또는 lock 파일 없음 →
      npm
  • 마이그레이션 후 추가 설정(ESLint + Prettier, VSCode, Tailwind 통합 등)이 필요하다면
    react-next-scaffold
    스킬을 이어서 실행하면 된다.
  • 路由结构和页面的客户端/服务端组件属性会自动判断,存在歧义时为了安全性会优先添加
    'use client'
    标识
  • 除了路由、环境变量、导入路径之外,原有组件代码不会做额外修改
  • 包管理器按照项目现有lock文件判断:
    • pnpm-lock.yaml
      pnpm
    • yarn.lock
      yarn
    • bun.lockb
      bun.lock
      bun
    • package-lock.json
      或无lock文件 →
      npm
  • 迁移后如果需要额外配置(ESLint + Prettier、VSCode、Tailwind集成等),可以继续执行
    react-next-scaffold
    技能。