代码语言

知识点思维导图

19 个知识节点

工程化脚手架(03) - NextJS 模板

读完后,你应能完成以下任务:

  • 绘制“工程化脚手架(03) - NextJS 模板 / 概述”的关键对象与数据流,解释“Imber CLI 的 NextJS 模板是一个现代化的 React 全栈开发模板,基于 Next.js 14+ 和最新的 React 18 特性构建。”,并用源码位置、日志或 Trace 标注证据。
  • 为“工程化脚手架(03) - NextJS 模板 / 模板特性”设计正常与异常输入,验证“TypeScript:完整的类型安全支持”,输出首个偏差位置与回归测试结果。
  • 实现“工程化脚手架(03) - NextJS 模板 / 补充结论 1”的最小代码或配置,检验“PWA 支持:渐进式 Web 应用”,输出命令、结果与 Diff,并说明不适用边界。

一、概述

Imber CLI 的 NextJS 模板是一个现代化的 React 全栈开发模板,基于 Next.js 14+ 和最新的 React 18 特性构建。该模板集成了 TypeScript、Tailwind CSS、ESLint、Prettier 等现代开发工具,为开发者提供了一个开箱即用的生产级项目基础。

二、模板特性

2.1 核心特性

  • Next.js 14+:最新的 App Router 和 Server Components
  • TypeScript:完整的类型安全支持
  • Tailwind CSS:原子化 CSS 框架
  • ESLint + Prettier:代码质量和格式化
  • Jest + Testing Library:单元测试和集成测试
  • Storybook:组件开发和文档
  • PWA 支持:渐进式 Web 应用
  • SEO 优化:内置 SEO 最佳实践

2.2 技术栈

{
  "dependencies": {
    "next": "^14.0.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "@types/node": "^20.0.0",
    "@types/react": "^18.2.0",
    "@types/react-dom": "^18.2.0",
    "typescript": "^5.0.0",
    "tailwindcss": "^3.3.0",
    "autoprefixer": "^10.4.0",
    "postcss": "^8.4.0"
  },
  "devDependencies": {
    "eslint": "^8.0.0",
    "eslint-config-next": "^14.0.0",
    "prettier": "^3.0.0",
    "@testing-library/react": "^13.4.0",
    "@testing-library/jest-dom": "^5.16.0",
    "jest": "^29.0.0",
    "jest-environment-jsdom": "^29.0.0"
  }
}

三、项目结构

3.1 目录结构

{{projectNameKebab}}/
├── src/
│   ├── app/                    # App Router 目录
│   │   ├── globals.css        # 全局样式
│   │   ├── layout.tsx         # 根布局
│   │   ├── page.tsx           # 首页
│   │   ├── loading.tsx        # 加载页面
│   │   ├── error.tsx          # 错误页面
│   │   └── not-found.tsx      # 404 页面
│   ├── components/            # 可复用组件
│   │   ├── ui/               # 基础 UI 组件
│   │   ├── layout/           # 布局组件
│   │   └── common/           # 通用组件
│   ├── lib/                   # 工具库
│   │   ├── utils.ts          # 工具函数
│   │   ├── validations.ts    # 表单验证
│   │   └── constants.ts       # 常量定义
│   ├── hooks/                 # 自定义 Hooks
│   ├── types/                 # TypeScript 类型
│   └── styles/                # 样式文件
├── public/                    # 静态资源
├── stories/                   # Storybook 故事
├── __tests__/                 # 测试文件
├── .env.local                 # 环境变量
├── .gitignore                 # Git 忽略文件
├── next.config.js            # Next.js 配置
├── tailwind.config.js        # Tailwind 配置
├── tsconfig.json             # TypeScript 配置
├── jest.config.js            # Jest 配置
└── package.json              # 项目配置

四、核心配置

4.1 Next.js 配置

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    appDir: true,
    serverComponentsExternalPackages: ['@prisma/client']
  },
  images: {
    domains: ['example.com'],
    formats: ['image/webp', 'image/avif']
  },
  compiler: {
    removeConsole: process.env.NODE_ENV === 'production'
  },
  poweredByHeader: false,
  compress: true,
  swcMinify: true
}

module.exports = nextConfig

4.2 TypeScript 配置

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "lib": ["dom", "dom.iterable", "es6"],
    "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/*"],
      "@/components/*": ["./src/components/*"],
      "@/lib/*": ["./src/lib/*"],
      "@/hooks/*": ["./src/hooks/*"],
      "@/types/*": ["./src/types/*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}

4.3 Tailwind CSS 配置

// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
    './src/components/**/*.{js,ts,jsx,tsx,mdx}',
    './src/app/**/*.{js,ts,jsx,tsx,mdx}'
  ],
  theme: {
    extend: {
      colors: {
        primary: {
          50: '#eff6ff',
          500: '#3b82f6',
          900: '#1e3a8a'
        }
      },
      fontFamily: {
        sans: ['Inter', 'system-ui', 'sans-serif']
      },
      animation: {
        'fade-in': 'fadeIn 0.5s ease-in-out',
        'slide-up': 'slideUp 0.3s ease-out'
      },
      keyframes: {
        fadeIn: {
          '0%': { opacity: '0' },
          '100%': { opacity: '1' }
        },
        slideUp: {
          '0%': { transform: 'translateY(10px)', opacity: '0' },
          '100%': { transform: 'translateY(0)', opacity: '1' }
        }
      }
    }
  },
  plugins: [require('@tailwindcss/forms'), require('@tailwindcss/typography'), require('@tailwindcss/aspect-ratio')]
}

五、核心组件

5.1 根布局组件

// src/app/layout.tsx
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
  title: {
    default: '{{projectName}}',
    template: '%s | {{projectName}}'
  },
  description: '{{projectName}} - 现代化的 Next.js 应用',
  keywords: ['Next.js', 'React', 'TypeScript', 'Tailwind CSS'],
  authors: [{ name: '{{author}}' }],
  creator: '{{author}}',
  openGraph: {
    type: 'website',
    locale: 'zh_CN',
    url: 'https://example.com',
    title: '{{projectName}}',
    description: '{{projectName}} - 现代化的 Next.js 应用',
    siteName: '{{projectName}}'
  },
  twitter: {
    card: 'summary_large_image',
    title: '{{projectName}}',
    description: '{{projectName}} - 现代化的 Next.js 应用'
  },
  robots: {
    index: true,
    follow: true,
    googleBot: {
      index: true,
      follow: true,
      'max-video-preview': -1,
      'max-image-preview': 'large',
      'max-snippet': -1
    }
  }
}

export default function RootLayout({
  children
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="zh-CN" className={inter.className}>
      <body className="min-h-screen bg-background font-sans antialiased">
        <div className="relative flex min-h-screen flex-col">
          <div className="flex-1">{children}</div>
        </div>
      </body>
    </html>
  )
}

5.2 首页组件

// src/app/page.tsx
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'

export default function HomePage() {
  return (
    <div className="container mx-auto px-4 py-8">
      <div className="text-center">
        <h1 className="text-4xl font-bold tracking-tight sm:text-6xl">
          欢迎使用 <span className="text-primary">{{projectName}}</span>
        </h1>
        <p className="mt-6 text-lg leading-8 text-muted-foreground">
          基于 Next.js 14+ 和 TypeScript 构建的现代化 Web 应用
        </p>
        <div className="mt-10 flex items-center justify-center gap-x-6">
          <Button size="lg">开始使用</Button>
          <Button variant="outline" size="lg">
            查看文档
          </Button>
        </div>
      </div>

      <div className="mt-16 grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3">
        <Card>
          <CardHeader>
            <CardTitle>🚀 性能优化</CardTitle>
            <CardDescription>
              内置性能优化,支持 SSR、SSG 和 ISR
            </CardDescription>
          </CardHeader>
          <CardContent>
            <ul className="space-y-2 text-sm">
              <li>• 自动代码分割</li>
              <li>• 图片优化</li>
              <li>• 字体优化</li>
              <li>• 缓存策略</li>
            </ul>
          </CardContent>
        </Card>

        <Card>
          <CardHeader>
            <CardTitle>🎨 现代设计</CardTitle>
            <CardDescription>
              基于 Tailwind CSS 的响应式设计系统
            </CardDescription>
          </CardHeader>
          <CardContent>
            <ul className="space-y-2 text-sm">
              <li>• 响应式布局</li>
              <li>• 暗色模式</li>
              <li>• 无障碍访问</li>
              <li>• 动画效果</li>
            </ul>
          </CardContent>
        </Card>

        <Card>
          <CardHeader>
            <CardTitle>🛠️ 开发体验</CardTitle>
            <CardDescription>
              完整的开发工具链和最佳实践
            </CardDescription>
          </CardHeader>
          <CardContent>
            <ul className="space-y-2 text-sm">
              <li>• TypeScript 支持</li>
              <li>• ESLint + Prettier</li>
              <li>• 热重载</li>
              <li>• 调试工具</li>
            </ul>
          </CardContent>
        </Card>
      </div>

      <div className="mt-16">
        <h2 className="text-2xl font-bold text-center mb-8">技术栈</h2>
        <div className="flex flex-wrap justify-center gap-2">
          <Badge variant="secondary">Next.js 14+</Badge>
          <Badge variant="secondary">React 18</Badge>
          <Badge variant="secondary">TypeScript</Badge>
          <Badge variant="secondary">Tailwind CSS</Badge>
          <Badge variant="secondary">ESLint</Badge>
          <Badge variant="secondary">Prettier</Badge>
          <Badge variant="secondary">Jest</Badge>
          <Badge variant="secondary">Storybook</Badge>
        </div>
      </div>
    </div>
  )
}

5.3 UI 组件库

// src/components/ui/button.tsx
import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'

const buttonVariants = cva(
  'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
  {
    variants: {
      variant: {
        default: 'bg-primary text-primary-foreground hover:bg-primary/90',
        destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
        outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
        secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
        ghost: 'hover:bg-accent hover:text-accent-foreground',
        link: 'text-primary underline-offset-4 hover:underline'
      },
      size: {
        default: 'h-10 px-4 py-2',
        sm: 'h-9 rounded-md px-3',
        lg: 'h-11 rounded-md px-8',
        icon: 'h-10 w-10'
      }
    },
    defaultVariants: {
      variant: 'default',
      size: 'default'
    }
  }
)

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, asChild = false, ...props }, ref) => {
    const Comp = asChild ? Slot : 'button'
    return (
      <Comp
        className={cn(buttonVariants({ variant, size, className }))}
        ref={ref}
        {...props}
      />
    )
  }
)
Button.displayName = 'Button'

export { Button, buttonVariants }

六、开发工具配置

6.1 ESLint 配置

// .eslintrc.js
module.exports = {
  extends: ['next/core-web-vitals', 'eslint:recommended', '@typescript-eslint/recommended', 'prettier'],
  parser: '@typescript-eslint/parser',
  plugins: ['@typescript-eslint'],
  rules: {
    '@typescript-eslint/no-unused-vars': 'error',
    '@typescript-eslint/no-explicit-any': 'warn',
    'prefer-const': 'error',
    'no-var': 'error'
  },
  ignorePatterns: ['node_modules/', '.next/', 'out/']
}

6.2 Prettier 配置

// .prettierrc
{
  "semi": false,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "none",
  "printWidth": 80,
  "endOfLine": "lf"
}

6.3 Jest 配置

// jest.config.js
const nextJest = require('next/jest')

const createJestConfig = nextJest({
  dir: './'
})

const customJestConfig = {
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  moduleNameMapping: {
    '^@/(.*)$': '<rootDir>/src/$1'
  },
  testEnvironment: 'jest-environment-jsdom'
}

module.exports = createJestConfig(customJestConfig)

七、最佳实践

7.1 组件设计原则

// 组件应该遵循单一职责原则
interface ComponentProps {
  // 使用明确的类型定义
  title: string
  description?: string
  children?: React.ReactNode
  className?: string
}

// 使用 forwardRef 支持 ref 传递
const Component = React.forwardRef<HTMLDivElement, ComponentProps>(
  ({ title, description, children, className, ...props }, ref) => {
    return (
      <div ref={ref} className={cn('base-styles', className)} {...props}>
        <h2>{title}</h2>
        {description && <p>{description}</p>}
        {children}
      </div>
    )
  }
)

7.2 数据获取模式

// 使用 Server Components 进行数据获取
async function getData() {
  const res = await fetch('https://api.example.com/data', {
    next: { revalidate: 3600 } // 1小时缓存
  })

  if (!res.ok) {
    throw new Error('Failed to fetch data')
  }

  return res.json()
}

// 在 Server Component 中使用
export default async function Page() {
  const data = await getData()

  return (
    <div>
      <h1>{data.title}</h1>
      <p>{data.description}</p>
    </div>
  )
}

7.3 状态管理

// 使用 Zustand 进行状态管理
import { create } from 'zustand'

interface AppState {
  count: number
  increment: () => void
  decrement: () => void
}

const useAppStore = create<AppState>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 }))
}))

// 在组件中使用
function Counter() {
  const { count, increment, decrement } = useAppStore()

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  )
}

八、部署配置

8.1 Vercel 部署

// vercel.json
{
  "framework": "nextjs",
  "buildCommand": "npm run build",
  "devCommand": "npm run dev",
  "installCommand": "npm install",
  "regions": ["hkg1"],
  "functions": {
    "src/app/api/**/*.ts": {
      "maxDuration": 30
    }
  }
}

8.2 Docker 配置

# Dockerfile
FROM node:18-alpine AS base

# Install dependencies only when needed
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app

COPY package.json package-lock.json* ./
RUN npm ci

# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

RUN npm run build

# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app

ENV NODE_ENV production

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public

# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next

# Automatically leverage output traces to reduce image size
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT 3000
ENV HOSTNAME "0.0.0.0"

CMD ["node", "server.js"]

九、性能优化

9.1 图片优化

import Image from 'next/image'

// 使用 Next.js Image 组件
function OptimizedImage({ src, alt, width, height }) {
  return (
    <Image
      src={src}
      alt={alt}
      width={width}
      height={height}
      priority // 关键图片预加载
      placeholder="blur" // 模糊占位符
      blurDataURL="data:image/jpeg;base64,..." // 自定义占位符
    />
  )
}

9.2 代码分割

import dynamic from 'next/dynamic'

// 动态导入组件
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
  loading: () => <p>Loading...</p>,
  ssr: false // 禁用服务端渲染
})

// 条件加载
const AdminPanel = dynamic(() => import('./AdminPanel'), {
  loading: () => <p>Loading admin panel...</p>
})

十、总结

  • 概述:Imber CLI 的 NextJS 模板是一个现代化的 React 全栈开发模板,基于 Next.js 14+ 和最新的 React 18 特性构建。
  • 模板特性:TypeScript:完整的类型安全支持
  • 性能优化:pnpm Workspace
  • 核心特性:TypeScript:完整的类型安全支持

学完自测

选择所有正确答案;提交后逐项核对判断依据。

1在“NextJS 模板”中,需要同时满足“概述”与“核心特性”。给定正文约束“Imber CLI 的 NextJS 模板是一个现代化的 React 全栈开发模板,基于 Next.js 14+ 和最新的 React 18 特性构建。”,哪些判断保持了原有处理机制?多选
2“NextJS 模板”出现偏差:“在“NextJS 模板 / 该模板集成了 TypeScript、Tailwind CSS、E”中,即使不满足“该模板集成了 TypeScript、Tailwind CSS、ESLint、Prettier 等现代开发工具,为开发者提供了一个开箱即用的生产级项目基础”,结果与副作用仍”已成为实际行为。围绕“该模板集成了 TypeScript、Tailwind CSS、ESLint、Prettier 等现代开发工具,为开发者提供了一个开箱即用的生”与“TypeScript:完整的类型安全支持”,哪些判断能定位被改变的职责或边界?多选
3评审“NextJS 模板”方案时,验收条件包含“Tailwind CSS:原子化 CSS 框架”。关于“Tailwind CSS:原子化 CSS 框架”与“ESLint + Prettier:代码质量和格式化”的哪些决策符合正文机制?多选