12k
All articles

Как интегрировать ShadCN с Next.js

Интеграция Shadcn UI в проект Next.js с Tailwind CSS, тёмной темой через next-themes и совместимостью с React 19 выполняется пошаговой настройкой через CLI.

OpenReplay Team
OpenReplay Team
Как интегрировать ShadCN с Next.js

Настройка современной библиотеки UI-компонентов не должна напоминать сборку мебели без инструкции. Если вы разрабатываете с Next.js 15 и хотите красивые, настраиваемые компоненты без фрустрации от традиционных библиотек типа “черный ящик”, это руководство покажет вам точно, как интегрировать Shadcn UI в ваш проект.

Эта статья охватывает полный процесс настройки Shadcn UI с Next.js, от создания начального проекта до установки компонентов, конфигурации Tailwind CSS и правильной реализации темной темы. Вы узнаете, как избежать распространенных проблем совместимости с React 19 и настроить готовую к продакшену систему компонентов, которой вы действительно владеете.

Ключевые моменты

  • Shadcn UI копирует исходный код компонентов напрямую в ваш проект, предоставляя полный контроль над владением и настройкой
  • CLI обрабатывает установку и управление зависимостями, делая настройку простой даже с учетом особенностей совместимости React 19
  • Интеграция с Tailwind CSS происходит бесшовно, со встроенной поддержкой тем и реализацией темного режима
  • Компоненты не зависят от фреймворка и работают как с App Router, так и с Pages Router в Next.js

Почему стоит выбрать Shadcn UI для вашего Next.js проекта

Shadcn UI использует принципиально иной подход к библиотекам компонентов. Вместо установки зависимости, которую вы не можете изменить, Shadcn копирует исходный код компонентов напрямую в ваш проект. Это означает, что вы получаете:

  • Полное владение вашими компонентами - изменяйте что угодно, когда угодно
  • Дизайн с приоритетом Tailwind, который бесшовно интегрируется с вашими существующими стилями
  • Нулевые накладные расходы времени выполнения - компоненты это просто код в вашем проекте
  • Типобезопасность, встроенная с самого начала
  • Доступность, правильно обработанная по умолчанию через примитивы Radix UI

Этот подход особенно хорошо работает с App Router в Next.js 15, где серверные компоненты и границы клиента требуют тщательного проектирования компонентов.

Предварительные требования и начальная настройка

Перед началом настройки Shadcn UI с Next.js убедитесь, что у вас есть:

  • Node.js 18.17 или выше
  • Менеджер пакетов (npm, pnpm, yarn или bun)
  • Базовое знакомство с React и Tailwind CSS

Создание проекта Next.js 15

Начните с создания нового проекта Next.js с TypeScript и Tailwind CSS:

npx create-next-app@latest my-app --typescript --tailwind --app

При появлении запросов выберите следующие опции:

  • ✓ Would you like to use ESLint? Yes
  • ✓ Would you like to use src/ directory? Yes (рекомендуется)
  • ✓ Would you like to customize the default import alias? No

Перейдите в ваш проект:

cd my-app

Установка и конфигурация Shadcn UI

CLI Shadcn берет на себя большую часть тяжелой работы по установке компонентов. Запустите команду инициализации:

npx shadcn@latest init

Вы увидите несколько запросов конфигурации:

  1. Style: Выберите предпочитаемый стиль (Default/New York)
  2. Base color: Выберите из доступных цветовых схем
  3. CSS variables: Используйте Yes для лучшей поддержки тем

Обработка peer-зависимостей React 19

Если вы используете npm с React 19, вы столкнетесь с предупреждениями о peer-зависимостях:

npm error code ERESOLVE
npm error ERESOLVE unable to resolve dependency tree

CLI предложит вам выбрать стратегию разрешения:

? How would you like to proceed?
 Use --force
  Use --legacy-peer-deps

Выберите --legacy-peer-deps для наиболее безопасного подхода. Пользователи pnpm, yarn или bun не увидят этих предупреждений и могут продолжить обычным образом.

Добавление ваших первых компонентов

После инициализации Shadcn добавьте компоненты с помощью CLI:

npx shadcn@latest add button card

Эта команда:

  1. Загружает исходный код компонента
  2. Размещает его в src/components/ui/
  3. Устанавливает любые необходимые зависимости (например, примитивы Radix UI)
  4. Обновляет импорты ваших компонентов

Вот как выглядит типичный компонент кнопки Shadcn после установки:

// 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 rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2",
  {
    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",
        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 }

Обратите внимание, как компонент полностью настраиваемый - вы можете изменить варианты, добавить новые или полностью изменить стилизацию.

Конфигурация Tailwind CSS для Shadcn

Shadcn UI работает как с Tailwind CSS v3, так и с v4. CLI автоматически конфигурирует ваш tailwind.config.js во время инициализации. Вот что добавляется:

// tailwind.config.js
module.exports = {
  darkMode: ["class"],
  content: [
    './pages/**/*.{ts,tsx}',
    './components/**/*.{ts,tsx}',
    './app/**/*.{ts,tsx}',
    './src/**/*.{ts,tsx}',
  ],
  theme: {
    container: {
      center: true,
      padding: "2rem",
      screens: {
        "2xl": "1400px",
      },
    },
    extend: {
      colors: {
        border: "hsl(var(--border))",
        input: "hsl(var(--input))",
        ring: "hsl(var(--ring))",
        background: "hsl(var(--background))",
        foreground: "hsl(var(--foreground))",
        primary: {
          DEFAULT: "hsl(var(--primary))",
          foreground: "hsl(var(--primary-foreground))",
        },
        secondary: {
          DEFAULT: "hsl(var(--secondary))",
          foreground: "hsl(var(--secondary-foreground))",
        },
        destructive: {
          DEFAULT: "hsl(var(--destructive))",
          foreground: "hsl(var(--destructive-foreground))",
        },
        muted: {
          DEFAULT: "hsl(var(--muted))",
          foreground: "hsl(var(--muted-foreground))",
        },
        accent: {
          DEFAULT: "hsl(var(--accent))",
          foreground: "hsl(var(--accent-foreground))",
        },
        popover: {
          DEFAULT: "hsl(var(--popover))",
          foreground: "hsl(var(--popover-foreground))",
        },
        card: {
          DEFAULT: "hsl(var(--card))",
          foreground: "hsl(var(--card-foreground))",
        },
      },
      borderRadius: {
        lg: "var(--radius)",
        md: "calc(var(--radius) - 2px)",
        sm: "calc(var(--radius) - 4px)",
      },
    },
  },
  plugins: [require("tailwindcss-animate")],
}

Обновите ваш globals.css

CLI также обновляет ваш globals.css CSS-переменными для тематизации:

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 222.2 84% 4.9%;
    --card: 0 0% 100%;
    --card-foreground: 222.2 84% 4.9%;
    --popover: 0 0% 100%;
    --popover-foreground: 222.2 84% 4.9%;
    --primary: 221.2 83.2% 53.3%;
    --primary-foreground: 210 40% 98%;
    --secondary: 210 40% 96%;
    --secondary-foreground: 222.2 84% 4.9%;
    --muted: 210 40% 96%;
    --muted-foreground: 215.4 16.3% 46.9%;
    --accent: 210 40% 96%;
    --accent-foreground: 222.2 84% 4.9%;
    --destructive: 0 84.2% 60.2%;
    --destructive-foreground: 210 40% 98%;
    --border: 214.3 31.8% 91.4%;
    --input: 214.3 31.8% 91.4%;
    --ring: 221.2 83.2% 53.3%;
    --radius: 0.5rem;
  }

  .dark {
    --background: 222.2 84% 4.9%;
    --foreground: 210 40% 98%;
    --card: 222.2 84% 4.9%;
    --card-foreground: 210 40% 98%;
    --popover: 222.2 84% 4.9%;
    --popover-foreground: 210 40% 98%;
    --primary: 217.2 91.2% 59.8%;
    --primary-foreground: 222.2 84% 4.9%;
    --secondary: 217.2 32.6% 17.5%;
    --secondary-foreground: 210 40% 98%;
    --muted: 217.2 32.6% 17.5%;
    --muted-foreground: 215 20.2% 65.1%;
    --accent: 217.2 32.6% 17.5%;
    --accent-foreground: 210 40% 98%;
    --destructive: 0 62.8% 30.6%;
    --destructive-foreground: 210 40% 98%;
    --border: 217.2 32.6% 17.5%;
    --input: 217.2 32.6% 17.5%;
    --ring: 224.3 76.3% 94.1%;
  }
}

@layer base {
  * {
    @apply border-border;
  }
  body {
    @apply bg-background text-foreground;
  }
}

Реализация темного режима с next-themes

Для готовой к продакшену реализации темного режима используйте next-themes, чтобы избежать проблем с гидратацией.

Установите next-themes

npm install next-themes

Создайте провайдер темы

Создайте src/components/theme-provider.tsx:

"use client"

import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
import { type ThemeProviderProps } from "next-themes/dist/types"

export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
  return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}

Обновите ваш корневой макет

Измените src/app/layout.tsx:

import { ThemeProvider } from "@/components/theme-provider"

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <ThemeProvider
          attribute="class"
          defaultTheme="system"
          enableSystem
          disableTransitionOnChange
        >
          {children}
        </ThemeProvider>
      </body>
    </html>
  )
}

Создайте компонент переключения темы

"use client"

import { Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes"
import { Button } from "@/components/ui/button"

export function ThemeToggle() {
  const { setTheme, theme } = useTheme()

  return (
    <Button
      variant="ghost"
      size="icon"
      onClick={() => setTheme(theme === "light" ? "dark" : "light")}
    >
      <Sun className="h-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
      <Moon className="absolute h-5 w-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
      <span className="sr-only">Toggle theme</span>
    </Button>
  )
}

Работа с изменениями React 19

React 19 вводит несколько изменений, которые влияют на то, как вы работаете с компонентами Shadcn:

Упрощенная обработка ref

React 19 позволяет передавать ref как обычный проп, устраняя необходимость в forwardRef во многих случаях:

// Старый подход (React 18)
const Input = React.forwardRef<HTMLInputElement, InputProps>(
  ({ className, ...props }, ref) => {
    return <input ref={ref} className={className} {...props} />
  }
)

// Новый подход (React 19)
function Input({ className, ref, ...props }: InputProps & { ref?: React.Ref<HTMLInputElement> }) {
  return <input ref={ref} className={className} {...props} />
}

Новые хуки форм

Хуки useActionState и useFormStatus из React 19 бесшовно работают с компонентами форм Shadcn:

import { useActionState } from "react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"

function ContactForm() {
  const [state, formAction] = useActionState(async (prevState, formData) => {
    // Логика серверного действия здесь
    const email = formData.get("email")
    // Обработка формы...
    return { success: true }
  }, { success: false })

  return (
    <form action={formAction}>
      <Input name="email" type="email" required />
      <Button type="submit">Submit</Button>
      {state.success && <p>Thanks for subscribing!</p>}
    </form>
  )
}

Распространенные проблемы и решения

Ошибки сборки с Tailwind CSS

Если вы сталкиваетесь с ошибками сборки после настройки, убедитесь, что ваша конфигурация PostCSS корректна:

// postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

Стили компонентов не применяются

Проверьте, что ваш globals.css импортирован в корневом макете:

import "./globals.css"

Ошибки TypeScript с пропами компонентов

Убедитесь, что ваш tsconfig.json включает корректные пути:

{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

Лучшие практики для продакшена

  1. Настройте утилиту cn: Функция cn в lib/utils.ts интеллектуально объединяет классы. Расширьте ее для ваших специфических нужд.

  2. Создавайте варианты компонентов: Используйте CVA (class-variance-authority) для создания согласованных вариантов компонентов по всему приложению.

  3. Оптимизируйте размер бандла: Устанавливайте только те компоненты, которые вы действительно используете. Каждый компонент независим.

  4. Тестируйте доступность: Компоненты Shadcn используют примитивы Radix UI, которые обрабатывают доступность, но всегда тестируйте со скринридерами.

  5. Контроль версий: Поскольку компоненты копируются в ваш проект, фиксируйте их в системе контроля версий и отслеживайте изменения.

Заключение

Настройка Shadcn UI с Next.js дает вам современную систему компонентов, которая балансирует гибкость со скоростью разработки. В отличие от традиционных библиотек компонентов, вы владеете каждой строкой кода, можете настроить что угодно и не привязаны к чужим дизайнерским решениям.

Комбинация App Router из Next.js 15, улучшений React 19 и Tailwind CSS создает мощную основу для создания современных веб-приложений. С подходом Shadcn к копированию компонентов вместо их установки как зависимостей, вы получаете лучшее из обоих миров: быструю разработку и полный контроль.

FAQ

В чем разница между использованием npm и pnpm для установки Shadcn UI?

При использовании npm с React 19 вам потребуется использовать флаг --legacy-peer-deps во время установки компонентов из-за конфликтов peer-зависимостей. Менеджеры пакетов типа pnpm, yarn и bun обрабатывают эти зависимости более грациозно и не требуют специальных флагов. Конечный результат одинаков независимо от выбранного менеджера пакетов.

Могу ли я использовать Shadcn UI с Pages Router в Next.js?

Да, Shadcn UI работает как с App Router, так и с Pages Router. Код компонентов сам по себе не зависит от фреймворка. Основная разница заключается в том, как вы реализуете провайдеры типа провайдера темы - в Pages Router вы бы обернули ваше приложение в _app.tsx вместо корневого макета.

Как настроить стили компонентов Shadcn по умолчанию?

Поскольку Shadcn копирует компоненты напрямую в ваш проект, вы можете изменять их как любой другой код. Откройте файл компонента в src/components/ui/, настройте классы Tailwind или варианты CVA и сохраните. Вы также можете изменить глобальные переменные темы в вашем CSS, чтобы изменить цвета и отступы для всех компонентов сразу.

Нужно ли устанавливать все компоненты Shadcn сразу?

Нет, вы должны устанавливать компоненты только по мере необходимости. Каждый компонент независим и включает свои собственные зависимости. Это сохраняет размер вашего бандла минимальным, а проект организованным. Используйте npx shadcn@latest add [component-name] для добавления отдельных компонентов при необходимости.

Что происходит, когда Shadcn UI обновляет компонент?

Поскольку компоненты копируются в ваш проект, они не будут автоматически обновляться при выпуске новых версий Shadcn. Вы можете вручную проверить документацию Shadcn UI на предмет обновлений и либо повторно запустить команду add для конкретных компонентов, либо вручную применить изменения. Это дает вам контроль над тем, когда и как компоненты обновляются в вашем проекте.

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers

We use cookies to improve your experience. By using our site, you accept cookies.