Jak skonfigurować projekt Next.js z TypeScript i Tailwind od zera

Praktyczny przewodnik konfiguracji Next.js 16 z TypeScript i Tailwind CSS v4. Struktura folderów, aliasy, linting i sensowne ustawienia startowe bez zbędnego balastu.

Opublikowano

17 czerwca 2025 12:09

Czytanie

5 min czytania

Aktualizacja

15 kwietnia 2026 11:52

Zaczynasz nowy projekt i chcesz użyć Next.js z TypeScript i Tailwind CSS? Dobry wybór — to stack, który sam stosuję w większości projektów komercyjnych. Szybki, typowany, z doskonałym developer experience.

W tym tutorialu przeprowadzę Cię przez konfigurację od zera. Nie tylko uruchomienie create-next-app, ale też sensowna struktura folderów, aliasy importów, formatowanie kodu i kilka ustawień, które oszczędzą Ci problemów w przyszłości.

Krótka odpowiedź: Stwórz projekt przez create-next-app z TypeScript, ESLint, Tailwind CSS, katalogiem src/ i App Routerem, a następnie dodaj alias @/*, skonfiguruj Prettier z pluginem do sortowania klas Tailwind oraz przemyślaną strukturę folderów (components/ui, components/features, lib, hooks, types, utils). Kluczowe narzędzia to helper cn() łączący clsx i tailwind-merge oraz next/font do optymalnego ładowania fontów. Taki setup daje solidną bazę pod projekty komercyjne bez zbędnego balastu.

Wymagania wstępne

Zanim zaczniemy, upewnij się, że masz:

  • Node.js 20.9 lub nowszy (sprawdź: node -v)
  • npm, yarn lub pnpm
  • Edytor kodu (polecam VS Code z rozszerzeniami Tailwind CSS IntelliSense i ES7+ React)

Krok 1: Tworzenie projektu

Najszybszy sposób to oficjalne CLI Next.js:

Code
npx create-next-app@latest moj-projekt

Kreator zada kilka pytań. Oto moje rekomendowane odpowiedzi:

Code
✔ Would you like to use TypeScript? Yes
✔ Would you like to use ESLint? Yes
✔ Would you like to use Tailwind CSS? Yes
✔ Would you like to use `src/` directory? Yes
✔ Would you like to use App Router? Yes
✔ Would you like to customize the default import alias? Yes
  What import alias would you like configured? @/*

Dlaczego te wybory?

  • TypeScript — bezpieczeństwo typów, lepsze autouzupełnianie, mniej bugów
  • ESLint — wyłapuje błędy i wymusza spójny styl
  • Tailwind CSS — szybkie stylowanie bez pisania CSS
  • src/ directory — oddziela kod źródłowy od plików konfiguracyjnych
  • App Router — nowy router Next.js z React Server Components (więcej o tym, kiedy wybrać App Router, a kiedy Pages Router)
  • Alias @/* — czyste importy zamiast ../../../components
Code
cd moj-projekt
npm run dev

Otwórz http://localhost:3000 — powinieneś zobaczyć domyślną stronę Next.js.

Krok 2: Struktura folderów

Domyślna struktura jest minimalna. Rozbuduj ją od razu, zanim projekt urośnie:

Code
moj-projekt/
├── public/
│   ├── fonts/
│   └── images/
├── src/
│   ├── app/
│   │   ├── (marketing)/      # grupa tras bez wpływu na URL
│   │   │   ├── page.tsx
│   │   │   └── layout.tsx
│   │   ├── blog/
│   │   │   ├── [slug]/
│   │   │   │   └── page.tsx
│   │   │   └── page.tsx
│   │   ├── layout.tsx        # główny layout
│   │   ├── globals.css
│   │   └── not-found.tsx
│   ├── components/
│   │   ├── ui/               # przyciski, inputy, karty
│   │   ├── layout/           # header, footer, sidebar
│   │   └── features/         # złożone komponenty domenowe
│   ├── lib/                  # helpery, konfiguracje
│   ├── hooks/                # custom hooks
│   ├── types/                # globalne typy TypeScript
│   └── utils/                # funkcje utility
├── tailwind.config.ts
├── tsconfig.json
└── package.json

Kilka zasad, które stosuję:

  • components/ui/ — małe, reużywalne komponenty (Button, Input, Card)
  • components/features/ — złożone komponenty specyficzne dla projektu
  • lib/ — konfiguracje zewnętrznych bibliotek, klienty API, czyli Application Programming Interface, definiuje sposób komunikacji między aplikacjami lub modułami.
  • utils/ — czyste funkcje (formatowanie dat, walidacja, cn() dla Tailwinda)

Krok 3: Konfiguracja TypeScript

Plik tsconfig.json jest już skonfigurowany, ale warto dodać kilka opcji:

Code
{
  "compilerOptions": {
    "target": "ES2017",
    "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"
      }
    ],
    "paths": {
      "@/*": ["./src/*"]
    },
    "baseUrl": "."
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}

Kluczowe ustawienia:

  • strict: true — włącza wszystkie ścisłe sprawdzenia TypeScript
  • paths — alias @/* mapuje na folder src/

Teraz zamiast:

Code
import { Button } from '../../../components/ui/Button'

Piszesz:

Code
import { Button } from '@/components/ui/Button'

Krok 4: Konfiguracja Tailwind CSS

Tailwind CSS v4 przenosi sporą część konfiguracji z tailwind.config.ts do CSS. W prostych projektach plik konfiguracyjny nie jest już potrzebny, a cała konfiguracja trafia do src/app/globals.css:

Code
/* src/app/globals.css */
@import "tailwindcss";
 
@theme {
  --color-brand-50: #f0f9ff;
  --color-brand-100: #e0f2fe;
  --color-brand-500: #0ea5e9;
  --color-brand-600: #0284c7;
  --color-brand-700: #0369a1;
  --font-family-sans: var(--font-inter), system-ui, sans-serif;
  --animate-fade-in: fadeIn 0.5s ease-in-out;
}
 
@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}
 
@layer base {
  body {
    @apply bg-white text-gray-900 antialiased;
  }
}
 
@layer components {
  .container {
    @apply mx-auto max-w-7xl px-4 sm:px-6 lg:px-8;
  }
}

Klasy niestandardowe (np. bg-brand-600, font-sans) działają od razu po zdefiniowaniu zmiennych w @theme. Tailwind v4 automatycznie wykrywa pliki do skanowania — nie trzeba konfigurować tablicy content.

Jeśli używasz Tailwind CSS v4, upewnij się też, że PostCSS jest skonfigurowany pod nowy plugin:

Code
// postcss.config.mjs
const config = {
  plugins: {
    '@tailwindcss/postcss': {},
  },
}
 
export default config

Krok 5: Helper cn() dla klas Tailwind

Zainstaluj biblioteki do łączenia klas:

Code
npm install clsx tailwind-merge

Utwórz src/utils/cn.ts:

Code
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'
 
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

Użycie:

Code
import { cn } from '@/utils/cn'
 
function Button({ variant, className, ...props }) {
  return (
    <button
      className={cn(
        'rounded-lg px-4 py-2 font-medium',
        variant === 'primary' && 'bg-brand-600 text-white',
        variant === 'secondary' && 'bg-gray-100 text-gray-900',
        className,
      )}
      {...props}
    />
  )
}

Krok 6: ESLint i Prettier

Next.js instaluje ESLint, ale warto dodać Prettier do formatowania:

Code
npm install -D prettier prettier-plugin-tailwindcss eslint-config-prettier

Utwórz .prettierrc:

Code
{
  "semi": false,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5",
  "plugins": ["prettier-plugin-tailwindcss"]
}

Plugin prettier-plugin-tailwindcss automatycznie sortuje klasy Tailwind w zalecanej kolejności. Więcej o Tailwind znajdziesz w artykule 10 trików, które przyspieszą Twoją pracę.

W nowych projektach Next.js używa flat config. Zamiast starego .eslintrc.json edytujesz eslint.config.mjs:

Code
// eslint.config.mjs
import { defineConfig, globalIgnores } from 'eslint/config'
import nextVitals from 'eslint-config-next/core-web-vitals'
import prettierConfig from 'eslint-config-prettier'
 
export default defineConfig([
  ...nextVitals,
  prettierConfig,
  {
    rules: {
      'react/no-unescaped-entities': 'off',
    },
  },
  globalIgnores(['.next/**', 'out/**', 'build/**', 'next-env.d.ts']),
])

Dodaj skrypty do package.json:

Code
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint .",
    "format": "prettier --write ."
  }
}

Krok 7: Fonty z next/font

Next.js ma wbudowaną optymalizację fontów. Skonfiguruj w głównym layoucie:

Code
// src/app/layout.tsx
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'
 
const inter = Inter({
  subsets: ['latin', 'latin-ext'],
  variable: '--font-inter',
})
 
export const metadata: Metadata = {
  title: {
    default: 'Mój Projekt',
    template: '%s | Mój Projekt',
  },
  description: 'Opis projektu dla wyszukiwarek',
}
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="pl" className={inter.variable}>
      <body className="font-sans">{children}</body>
    </html>
  )
}

Font jest ładowany optymalnie, bez CLS, czyli Cumulative Layout Shift, mierzy nieoczekiwane przesunięcia elementów podczas ładowania strony. i z automatycznym preloadem.

Krok 8: Przykładowy komponent

Stwórzmy prosty komponent Button, żeby sprawdzić, czy wszystko działa:

Code
// src/components/ui/Button.tsx
import { cn } from '@/utils/cn'
import { ButtonHTMLAttributes } from 'react'
 
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'primary' | 'secondary' | 'ghost'
  size?: 'sm' | 'md' | 'lg'
}
 
export function Button({
  className,
  variant = 'primary',
  size = 'md',
  ...props
}: ButtonProps) {
  return (
    <button
      className={cn(
        'inline-flex items-center justify-center rounded-lg font-medium transition focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
        {
          'bg-brand-600 hover:bg-brand-700 focus:ring-brand-500 text-white':
            variant === 'primary',
          'bg-gray-100 text-gray-900 hover:bg-gray-200 focus:ring-gray-500':
            variant === 'secondary',
          'text-gray-600 hover:bg-gray-100 hover:text-gray-900 focus:ring-gray-500':
            variant === 'ghost',
        },
        {
          'px-3 py-1.5 text-sm': size === 'sm',
          'px-4 py-2 text-base': size === 'md',
          'px-6 py-3 text-lg': size === 'lg',
        },
        className,
      )}
      {...props}
    />
  )
}

Użycie na stronie:

Code
// src/app/page.tsx
import { Button } from '@/components/ui/Button'
 
export default function HomePage() {
  return (
    <main className="container py-12">
      <h1 className="mb-8 text-4xl font-bold">Witaj w projekcie</h1>
      <div className="flex gap-4">
        <Button variant="primary">Primary</Button>
        <Button variant="secondary">Secondary</Button>
        <Button variant="ghost">Ghost</Button>
      </div>
    </main>
  )
}

Krok 9: Uruchomienie i sprawdzenie

Code
npm run dev

Otwórz http://localhost:3000. Powinieneś zobaczyć stronę z trzema przyciskami w różnych wariantach.

Sprawdź też:

Code
npm run lint      # ESLint
npm run format    # Prettier
npm run build     # Produkcyjny build

Jeśli wszystko przechodzi bez błędów, projekt jest gotowy do rozwoju.

FAQ

Czy muszę używać katalogu src/ w Next.js?

Nie jest to obowiązkowe, ale bardzo zalecane. Katalog src/ oddziela kod źródłowy od plików konfiguracyjnych (package.json, tsconfig.json, postcss.config.mjs) w katalogu głównym, co sprawia, że projekt jest czytelniejszy i łatwiejszy w nawigacji, szczególnie gdy rośnie.

Jaka jest różnica między Tailwind CSS v3 a v4?

Tailwind CSS v4 przenosi konfigurację z pliku tailwind.config.ts do CSS — cała konfiguracja niestandardowych kolorów, fontów i animacji trafia do bloku @theme w pliku globals.css. Nie trzeba też konfigurować tablicy content, bo v4 automatycznie skanuje pliki projektu. PostCSS wymaga nowego pluginu @tailwindcss/postcss zamiast starszego tailwindcss.

Do czego służy helper cn() i dlaczego jest potrzebny?

Helper cn() łączy dwie biblioteki: clsx (do warunkowego łączenia klas) i tailwind-merge (do eliminacji konfliktujących klas Tailwinda, np. p-4 i p-8). Bez tailwind-merge Tailwind nie usuwa automatycznie nadpisanych klas — obie trafiają do DOM, co może powodować nieprzewidywalne style.

Czy strict: true w TypeScript jest konieczne?

Nie jest obowiązkowe, ale zdecydowanie zalecane od początku projektu. Włącza zestaw ścisłych sprawdzeń, takich jak noImplicitAny, strictNullChecks i strictFunctionTypes, które wymuszają precyzyjniejsze typowanie. Włączenie strict po tym, jak projekt już urósł, jest znacznie trudniejsze niż ustawienie go na starcie.

Jak działa alias @/* w importach?

Alias @/* jest konfigurowany w tsconfig.json i mapuje na katalog src/. Dzięki temu zamiast pisać import { Button } from '../../../components/ui/Button' możesz pisać import { Button } from '@/components/ui/Button'. Next.js automatycznie przekazuje tę konfigurację do Webpacka i TypeScript rozumie go w IDE.

Czy plugin prettier-plugin-tailwindcss działa z Tailwind v4?

Tak, plugin obsługuje Tailwind v4, ale upewnij się, że używasz aktualnej wersji pluginu (0.6.x lub nowszej). Plugin automatycznie sortuje klasy Tailwind w zalecanej kolejności podczas formatowania, co eliminuje ręczną pracę i sprawia, że diff w pull requestach jest czytelniejszy.

Kiedy warto dodać Storybook do projektu Next.js z tym setupem?

Storybook warto dodać, gdy projekt ma rosnącą bibliotekę komponentów UI, czyli User Interface, to wizualna i interakcyjna warstwa produktu. i potrzebuje ich izolowanego dokumentowania oraz testowania wizualnego. Przy strukturze components/ui/ opisanej w tym artykule Storybook integruje się naturalnie. Dla małych projektów i MVP narzut konfiguracyjny Storybooka może być zbyt duży — lepiej dodać go, gdy potrzeba jest realna.

Podsumowanie

Masz teraz solidną bazę do budowy aplikacji:

  • Next.js 16 z App Router i Server Components
  • TypeScript ze ścisłym typowaniem
  • Tailwind CSS z custom design system
  • ESLint + Prettier z automatycznym sortowaniem klas
  • Aliasy importów dla czystego kodu
  • Przemyślana struktura folderów gotowa na rozbudowę

Od tego punktu możesz dodawać kolejne elementy: autentykację, bazę danych z Prisma, CMS, czyli Content Management System, to system do zarządzania treścią bez ręcznej edycji kodu., testy. Fundament jest solidny.

Źródła i dokumentacja


Następny krok? Poznaj pobieranie danych w Next.js lub sprawdź, dlaczego Next.js jest najlepszy pod SEO.

Pracuję z tym zawodowo.

Jeśli chcesz przełożyć ten temat na lepszą architekturę frontendu, uporządkować React lub Next.js i podnieść jakość pracy zespołu, skontaktuj się ze mną. Pomagam zamieniać wiedzę z artykułów w praktyczne decyzje technologiczne.

O autorze

Maciej Sala

Maciej Sala — project manager i frontendowiec z doświadczeniem w marketingu internetowym. Na co dzień pracuję z Reactem, Next.js i TypeScriptem, łącząc perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat związany z branżą gier wideo jako project manager i game designer.

Absolwent historii na Uniwersytecie Jagiellońskim i studiów podyplomowych z marketingu internetowego na Akademii Górniczo-Hutniczej w Krakowie. Poza pracą trenuje na siłowni, maluje figurki i realizuje własne projekty.

Biblioteka wiedzy

Czytaj dalej

Zobacz więcej wpisów
Astro.js vs Next.js — które narzędzie wybrać w 2026 roku?

Astro.js vs Next.js — które narzędzie wybrać w 2026 roku?

Fachowe porównanie Astro.js i Next.js z perspektywy developera pracującego na co dzień w Next.js. Architektura, wydajność, SEO, DX, koszty i konkretne use case — z benchmarkami i przykładami kodu.

Maciej Sala

Maciej Sala

Founder Strivelab