react-vite-to-next-migration
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseVite 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 프로젝트 | |
| TypeScript | |
| React Router | |
| TanStack Query | |
| Zustand | |
| Tailwind CSS | |
| 환경 변수 | |
| 정적 자산 | |
| 진입점 | |
| 패키지 매니저 | |
vite.config.*通过检查以下文件判断是否支持迁移以及需要迁移的内容:
| 检测对象 | 检测方式 |
|---|---|
| Vite项目 | 存在 |
| TypeScript | 存在 |
| React Router | |
| TanStack Query | |
| Zustand | |
| Tailwind CSS | |
| 环境变量 | |
| 静态资源 | 存在 |
| 入口点 | 存在 |
| 包管理器 | |
如果不存在文件则不属于迁移对象,告知用户后终止操作。
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은 1단계에서 감지한 패키지 매니저로 대체한다. 존재하지 않는 패키지는{pm}대상에서 제외한다.remove
package.jsonscriptsjson
{
"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替换为第1步检测到的包管理器,不存在的包将从{pm}列表中排除。remove
将中的替换为Next.js标准配置:
package.jsonscriptsjson
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
}
}4단계: 설정 파일 교체
第4步:配置文件替换
다음 파일을 제거한다:
- /
vite.config.tsvite.config.js index.htmlsrc/vite-env.d.ts
next.config.tsts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
reactStrictMode: true
}
export default nextConfigtsconfig.jsonpathsjson
{
"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.tsvite.config.js index.htmlsrc/vite-env.d.ts
新建:
next.config.tsts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
reactStrictMode: true
}
export default nextConfig更新为Next.js适配版本(保留原有配置):
tsconfig.jsonpathsjson
{
"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.tsxindex.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>
)
}기존 를 로 이동.
Tailwind 사용 프로젝트라면 상단에 다음 지시문이 유지되어야 한다:
src/index.csssrc/app/globals.csscss
@import 'tailwindcss';src/main.tsxsrc/App.tsxsrc/App.css新建,将原有中的信息(标题、语言、元数据)迁移过来:
src/app/layout.tsxindex.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>
)
}将原有移动到。如果是使用Tailwind的项目,需要保留文件顶部的导入指令:
src/index.csssrc/app/globals.csscss
@import 'tailwindcss';src/main.tsxsrc/App.tsxsrc/App.css6단계: 라우팅 변환 (React Router → App Router)
第6步:路由转换(React Router → App Router)
기존 React Router 라우트 정의(/)를 분석한 뒤, 각 경로를 App Router 디렉토리 구조로 매핑한다.
createBrowserRouter<Routes>| React Router | App Router |
|---|---|
| |
| |
| |
| |
| 공통 레이아웃 컴포넌트 | |
각 페이지 컴포넌트는 다음 규칙으로 옮긴다:
- 기존 컴포넌트 본문은 최대한 그대로 유지
- ,
useNavigate,useLocation,useParams등 React Router API 사용 지점은 다음과 같이 치환:<Link>- →
useNavigate()(useRouter())next/navigation - → 페이지 컴포넌트의
useParams()prop (params페이지에서는async)await params - →
useLocation().pathname(usePathname())next/navigation - →
<Link to="...">(<Link href="...">)next/link
- 위 훅 중 하나라도 사용하거나, /
useState/이벤트 핸들러/브라우저 API를 사용하는 페이지는 파일 최상단에useEffect를 추가한다.'use client' - 그렇지 않은 정적 페이지는 서버 컴포넌트(기본값)로 둔다.
작업이 끝나면 , , , 기존 라우터 정의 파일( 등 라우팅 정의에만 쓰인 파일)을 제거한다. 단, 페이지 컴포넌트 본체는 로 옮겨지므로 보존된다.
src/main.tsxsrc/App.tsxsrc/App.csssrc/routes/index.tsxsrc/app/**/page.tsx解析原有React Router的路由定义(/),将每个路径映射为App Router的目录结构:
createBrowserRouter<Routes>| React Router | App Router |
|---|---|
| |
| |
| |
| |
| 公共布局组件 | |
每个页面组件按照以下规则迁移:
- 原有组件内容尽可能完整保留
- 所有使用React Router API的位置做如下替换:
- →
useNavigate()(来自useRouter())next/navigation - → 页面组件的
useParams()属性(异步页面中需使用params)await params - →
useLocation().pathname(来自usePathname())next/navigation - →
<Link to="...">(来自<Link href="...">)next/link
- 如果页面使用了上述任意Hook,或者使用了/
useState/事件处理函数/浏览器API,需要在文件顶部添加useEffect标识'use client' - 其余静态页面默认保留为服务端组件
操作完成后移除、、以及原有路由定义文件(如仅用于路由配置的等),页面组件本身会被迁移到保留。
src/main.tsxsrc/App.tsxsrc/App.csssrc/routes/index.tsxsrc/app/**/page.tsx7단계: 환경 변수 변환
第7步:环境变量转换
.env.env.local.env.development.env.production.env*- →
VITE_FOO=barNEXT_PUBLIC_FOO=bar
소스 코드 전반에서:
- →
import.meta.env.VITE_FOOprocess.env.NEXT_PUBLIC_FOO - →
import.meta.env.MODEprocess.env.NODE_ENV - →
import.meta.env.DEVprocess.env.NODE_ENV !== 'production' - →
import.meta.env.PRODprocess.env.NODE_ENV === 'production' - → 사용처 검토 후 제거 또는
import.meta.env.BASE_URL의next.config.ts로 대체basePath
在所有文件(、、、等)中做如下替换:
.env*.env.env.local.env.development.env.production- →
VITE_FOO=barNEXT_PUBLIC_FOO=bar
在所有源码中做如下替换:
- →
import.meta.env.VITE_FOOprocess.env.NEXT_PUBLIC_FOO - →
import.meta.env.MODEprocess.env.NODE_ENV - →
import.meta.env.DEVprocess.env.NODE_ENV !== 'production' - →
import.meta.env.PRODprocess.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适配(如果项目使用)
기존 플러그인은 3단계에서 이미 제거되었다. Next.js에서는 PostCSS 기반 설정으로 전환한다.
@tailwindcss/vitebash
{pm} install -D tailwindcss @tailwindcss/postcss postcss프로젝트 루트에 생성:
postcss.config.mjsjs
export default {
plugins: {
'@tailwindcss/postcss': {}
}
}src/app/globals.css@import 'tailwindcss';原有插件已在第3步移除,Next.js中切换为PostCSS基础配置:
@tailwindcss/vitebash
{pm} install -D tailwindcss @tailwindcss/postcss postcss在项目根目录新建:
postcss.config.mjsjs
export default {
plugins: {
'@tailwindcss/postcss': {}
}
}src/app/globals.css@import 'tailwindcss';10단계: TanStack Query (사용 중인 경우)
第10步:TanStack Query适配(如果项目使用)
기존 래핑 코드는 Next.js App Router 구조에 맞게 별도 Provider 컴포넌트로 분리한다. 자세한 구성은 스킬의 TanStack Query 단계와 동일하게 처리한다.
QueryClientProviderreact-next-scaffold요약:
- 추가 설치
{pm} install @tanstack/react-query-next-experimental - 생성 (
src/providers/query.tsx+'use client'+QueryClientProvider)ReactQueryStreamedHydration - 의
src/app/layout.tsx안쪽<body>을children로 래핑<QueryProvider>
原有封装代码需要按照Next.js App Router架构拆分到独立的Provider组件中,具体配置和技能的TanStack Query步骤一致。
QueryClientProviderreact-next-scaffold摘要:
- 新增安装依赖:
{pm} install @tanstack/react-query-next-experimental - 新建(包含
src/providers/query.tsx+'use client'+QueryClientProvider)ReactQueryStreamedHydration - 在的
src/app/layout.tsx标签内用<body>包裹<QueryProvider>children
11단계: Zustand (사용 중인 경우)
第11步:Zustand适配(如果项目使用)
기존 store 파일은 그대로 사용 가능하다. 단, store를 사용하는 컴포넌트는 클라이언트 컴포넌트여야 하므로 해당 파일 상단에 를 보장한다.
'use client'原有store文件可直接使用,需要确保使用store的组件是客户端组件,在对应文件顶部添加标识。
'use client'12단계: 정리 및 검증
第12步:清理与验证
다음 작업을 수행한다:
- 및 lock 파일 재생성:
node_modulesbashrm -rf node_modules {pm} install - 에
.gitignore,.next/추가 (없는 경우)next-env.d.ts - 빌드 검증:
bash
{pm} run build - 빌드 에러가 있을 경우, 에러 메시지에 따라 다음을 우선 점검:
- 서버 컴포넌트에서 브라우저 전용 API 사용 → 해당 컴포넌트에 추가
'use client' - 잔존 → 7단계 규칙으로 치환
import.meta.env - React Router API 잔존 → 6단계 규칙으로 치환
- 서버 컴포넌트에서 브라우저 전용 API 사용 → 해당 컴포넌트에
执行以下操作:
- 重新生成和lock文件:
node_modulesbashrm -rf node_modules {pm} install - 如果中不存在
.gitignore、.next/则添加next-env.d.ts - 构建验证:
bash
{pm} run build - 如果出现构建错误,优先按照以下规则排查:
- 服务端组件使用了浏览器专属API → 为对应组件添加标识
'use client' - 存在残留的→ 按照第7步规则替换
import.meta.env - 存在残留的React Router API → 按照第6步规则替换
- 服务端组件使用了浏览器专属API → 为对应组件添加
주의사항
注意事项
- 라우팅 구조와 페이지별 클라이언트/서버 컴포넌트 결정은 자동으로 판별하되, 모호한 경우 안전을 위해 를 추가한다.
'use client' - 기존 컴포넌트 코드는 라우팅/환경변수/임포트 경로 외에는 수정하지 않는다.
- 패키지 매니저는 기존 프로젝트의 lock 파일로 판별한다:
- →
pnpm-lock.yamlpnpm - →
yarn.lockyarn - 또는
bun.lockb→bun.lockbun - 또는 lock 파일 없음 →
package-lock.jsonnpm
- 마이그레이션 후 추가 설정(ESLint + Prettier, VSCode, Tailwind 통합 등)이 필요하다면 스킬을 이어서 실행하면 된다.
react-next-scaffold
- 路由结构和页面的客户端/服务端组件属性会自动判断,存在歧义时为了安全性会优先添加标识
'use client' - 除了路由、环境变量、导入路径之外,原有组件代码不会做额外修改
- 包管理器按照项目现有lock文件判断:
- →
pnpm-lock.yamlpnpm - →
yarn.lockyarn - 或
bun.lockb→bun.lockbun - 或无lock文件 →
package-lock.jsonnpm
- 迁移后如果需要额外配置(ESLint + Prettier、VSCode、Tailwind集成等),可以继续执行技能。
react-next-scaffold