Вивчай

Стилізація в Next.js

Next.js стилізація — студія дизайнера з тканинами палітрами кольорів та манекеномNext.js стилізація — студія дизайнера з тканинами палітрами кольорів та манекеном

Ми вже вміємо створювати сторінки, отримувати дані та будувати API. Тепер зробимо наш додаток гарним. Next.js підтримує кілька підходів до стилізації -- від класичного CSS до сучасного Tailwind. А ще має вбудовані інструменти для оптимізації шрифтів та зображень.


Глобальні стилі

Глобальні CSS-стилі імпортуються в кореневому layout.tsx:

/* src/app/globals.css */
*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: system-ui, -apple-system, sans-serif;
  line-height: 1.6;
  color: #333;
}

a {
  color: #0070f3;
  text-decoration: none;
}
// src/app/layout.tsx
import "./globals.css";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="uk">
      <body>{children}</body>
    </html>
  );
}
Увага

Глобальні стилі можна імпортувати тільки в layout.tsx або page.tsx -- не в звичайних компонентах. Для компонентів використовуй CSS Modules.


CSS Modules

CSS Modules -- це CSS-файли, де класи автоматично стають локальними (унікальними). Жодних конфліктів імен!

/* src/components/Card.module.css */
.card {
  border: 1px solid #eee;
  border-radius: 8px;
  padding: 1.5rem;
  transition: box-shadow 0.2s;
}

.card:hover {
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

.title {
  font-size: 1.25rem;
  font-weight: 600;
  margin-bottom: 0.5rem;
}

.description {
  color: #666;
  font-size: 0.9rem;
}
// src/components/Card.tsx
import styles from "./Card.module.css";

interface CardProps {
  title: string;
  description: string;
}

export default function Card({ title, description }: CardProps) {
  return (
    <div className={styles.card}>
      <h3 className={styles.title}>{title}</h3>
      <p className={styles.description}>{description}</p>
    </div>
  );
}

У браузері класи будуть виглядати як Card_card__x7f2k -- автоматично унікальні.

Комбінування класів

import styles from "./Button.module.css";

interface ButtonProps {
  variant: "primary" | "secondary";
  children: React.ReactNode;
}

export default function Button({ variant, children }: ButtonProps) {
  return (
    <button className={`${styles.button} ${styles[variant]}`}>
      {children}
    </button>
  );
}

Tailwind CSS

Tailwind CSS -- утилітарний CSS-фреймворк, який дозволяє стилізувати компоненти прямо в JSX через класи. При створенні проекту через create-next-app Tailwind вже налаштований.

Базові утиліти

export default function HeroSection() {
  return (
    <section className="bg-blue-600 text-white py-20 px-4">
      <div className="max-w-4xl mx-auto text-center">
        <h1 className="text-4xl font-bold mb-4">
          Вивчай веб-розробку
        </h1>
        <p className="text-xl text-blue-100 mb-8">
          Від HTML до Next.js -- повний курс для початківців
        </p>
        <button className="bg-white text-blue-600 px-6 py-3 rounded-lg font-semibold hover:bg-blue-50 transition-colors">
          Почати навчання
        </button>
      </div>
    </section>
  );
}

Responsive design

Tailwind використовує mobile-first підхід з брейкпоінтами:

export default function Grid() {
  return (
    <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
      <div className="p-4 bg-gray-100 rounded">Картка 1</div>
      <div className="p-4 bg-gray-100 rounded">Картка 2</div>
      <div className="p-4 bg-gray-100 rounded">Картка 3</div>
    </div>
  );
}
  • grid-cols-1 -- 1 колонка на мобільних
  • md:grid-cols-2 -- 2 колонки від 768px
  • lg:grid-cols-3 -- 3 колонки від 1024px

Dark mode

export default function ThemeCard() {
  return (
    <div className="bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 p-6 rounded-lg shadow">
      <h3 className="text-lg font-semibold">Картка</h3>
      <p className="text-gray-600 dark:text-gray-400">
        Автоматично адаптується до теми.
      </p>
    </div>
  );
}
Порада

Tailwind генерує тільки ті класи, які ти використовуєш. Фінальний CSS-файл зазвичай менше 10 КБ -- набагато менше, ніж типовий CSS-фреймворк.


next/font -- оптимізація шрифтів

Next.js має вбудовану оптимізацію шрифтів -- без зовнішніх запитів та без зсуву контенту (CLS):

Google Fonts

// src/app/layout.tsx
import { Inter, Roboto_Mono } from "next/font/google";

const inter = Inter({
  subsets: ["latin", "cyrillic"],
  variable: "--font-inter",
});

const robotoMono = Roboto_Mono({
  subsets: ["latin"],
  variable: "--font-roboto-mono",
});

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="uk" className={`${inter.variable} ${robotoMono.variable}`}>
      <body className={inter.className}>{children}</body>
    </html>
  );
}

Використання з Tailwind:

// tailwind.config.ts
import type { Config } from "tailwindcss";

const config: Config = {
  theme: {
    extend: {
      fontFamily: {
        sans: ["var(--font-inter)", "system-ui", "sans-serif"],
        mono: ["var(--font-roboto-mono)", "monospace"],
      },
    },
  },
};

export default config;
<h1 className="font-sans text-2xl">Заголовок шрифтом Inter</h1>
<code className="font-mono">Код шрифтом Roboto Mono</code>
Інфо

Next.js завантажує шрифти під час збірки і хостить їх локально. Браузер не робить запитів до Google Fonts -- це покращує приватність і швидкість.

Локальні шрифти

import localFont from "next/font/local";

const myFont = localFont({
  src: "./fonts/MyFont.woff2",
  variable: "--font-my",
});

next/image -- оптимізація зображень

Компонент Image автоматично оптимізує зображення:

import Image from "next/image";

export default function Avatar() {
  return (
    <Image
      src="/avatar.jpg"
      alt="Фото профілю"
      width={150}
      height={150}
      className="rounded-full"
    />
  );
}

Що робить Image:

  • Автоматично обирає формат (WebP, AVIF)
  • Lazy loading (завантажує, коли зображення наближається до viewport)
  • Запобігає зсуву контенту (CLS) -- резервує місце
  • Адаптивні розміри для різних екранів

Зображення на всю ширину

import Image from "next/image";

export default function HeroBanner() {
  return (
    <div className="relative w-full h-[400px]">
      <Image
        src="/banner.jpg"
        alt="Банер"
        fill
        className="object-cover"
        priority // Завантажити одразу (для above-the-fold)
      />
    </div>
  );
}

Зовнішні зображення

Для зовнішніх URL потрібно додати домен в конфігурацію:

// next.config.ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: "https",
        hostname: "api.example.com",
      },
    ],
  },
};

export default nextConfig;
<Image
  src="https://api.example.com/photos/1.jpg"
  alt="Зовнішнє зображення"
  width={600}
  height={400}
/>

Metadata API

Next.js має вбудований API для SEO-метаданих:

Статичні metadata

// src/app/layout.tsx
import type { Metadata } from "next";

export const metadata: Metadata = {
  title: {
    default: "Мій сайт",
    template: "%s | Мій сайт", // Для дочірніх сторінок
  },
  description: "Навчальний проект на Next.js",
  openGraph: {
    title: "Мій сайт",
    description: "Навчальний проект на Next.js",
    locale: "uk_UA",
    type: "website",
  },
};
// src/app/about/page.tsx
import type { Metadata } from "next";

export const metadata: Metadata = {
  title: "Про нас", // Стане "Про нас | Мій сайт" (через template)
  description: "Дізнайтеся більше про нас",
};

Динамічні metadata

// src/app/blog/[slug]/page.tsx
import type { Metadata } from "next";

export async function generateMetadata({
  params,
}: {
  params: Promise<{ slug: string }>;
}): Promise<Metadata> {
  const { slug } = await params;
  const post = await getPostBySlug(slug);

  return {
    title: post.title,
    description: post.description,
    openGraph: {
      title: post.title,
      images: [post.coverImage],
    },
  };
}

Підсумок

  • Глобальні стилі імпортуються в layout.tsx
  • CSS Modules (.module.css) -- локальні стилі без конфліктів
  • Tailwind CSS -- утилітарні класи прямо в JSX, mobile-first, dark mode
  • next/font -- оптимізація шрифтів (Google Fonts, локальні), без CLS
  • next/image -- оптимізація зображень (формати, lazy loading, адаптивність)
  • Metadata API -- SEO метадані (статичні та динамічні)

Що далі?

У наступному, фінальному уроці блоку вивчимо deployment -- як збілдити і задеплоїти Next.js додаток на Vercel або Netlify.

Інфо

Корисні посилання: