← React/Введение в Next.js#275 из 383← ПредыдущийСледующий →+30 XP
Полезно по теме:Гайд: React или VueПрактика: React setТермин: React HooksТема: React: хуки и экосистема

Введение в Next.js

Что такое Next.js

Next.js — это React-фреймворк от Vercel. Если React — это движок, то Next.js — это готовый автомобиль: маршрутизация из коробки, серверный рендеринг, оптимизация изображений, API-маршруты и многое другое.

Что добавляет Next.js к React:

  • Файловая маршрутизация (папки = маршруты)
  • Server-Side Rendering (SSR) и Static Generation (SSG)
  • Server Components (React код на сервере)
  • API Routes (бэкенд прямо в проекте)
  • Оптимизация изображений (next/image)
  • Оптимизация шрифтов (next/font)
  • Деплой в один клик на Vercel
  • App Router vs Pages Router

    Next.js имеет два роутера:

    | | Pages Router | App Router (актуальный) |

    |---|---|---|

    | Папка | /pages | /app |

    | Данные | getServerSideProps / getStaticProps | async Server Components |

    | Компоненты | Всё клиентское | Server Components по умолчанию |

    | Версия | До Next.js 12 | Next.js 13+ (рекомендуется) |

    Server Components vs Client Components

    Ключевое нововведение Next.js 13+ (App Router):

    // Server Component (по умолчанию в /app) — рендерится на сервере
    // Может: async/await, обращаться к БД напрямую, читать файлы
    // Не может: useState, useEffect, обработчики событий, browser API
    
    async function UserProfile({ id }) {
      // Прямой запрос к БД — без fetch, без useEffect, без loading state!
      const user = await db.users.findById(id)
    
      return (
        <div>
          <h1>{user.name}</h1>
          <InteractiveButton userId={id} />  {/* клиентский компонент */}
        </div>
      )
    }
    
    // Client Component — 'use client' директива вверху файла
    'use client'
    function InteractiveButton({ userId }) {
      const [liked, setLiked] = useState(false)  // useState — только в клиентском!
    
      return <button onClick={() => setLiked(!liked)}>
        {liked ? 'Понравилось' : 'Нравится'}
      </button>
    }

    Файловая маршрутизация в App Router

    app/
      page.tsx           -> /
      about/
        page.tsx         -> /about
      blog/
        page.tsx         -> /blog
        [slug]/
          page.tsx       -> /blog/любой-slug
      dashboard/
        layout.tsx       -> общий layout для /dashboard/*
        page.tsx         -> /dashboard
        settings/
          page.tsx       -> /dashboard/settings
    // app/blog/[slug]/page.tsx
    export default async function BlogPost({ params }) {
      const { slug } = params  // URL параметр из папки [slug]
      const post = await fetchPost(slug)
    
      return <article>{post.content}</article>
    }
    
    // Статическая генерация: список всех возможных slug
    export async function generateStaticParams() {
      const posts = await fetchAllPosts()
      return posts.map(post => ({ slug: post.slug }))
    }

    Сравнение: CSR vs SSR vs SSG

    CSR (Client-Side Rendering) — React по умолчанию:

  • Браузер загружает пустой HTML + JS бандл
  • JS выполняется, компонент монтируется, делает fetch
  • Пользователь видит лоадер, потом данные
  • Плохо для SEO (боты видят пустой HTML)
  • SSR (Server-Side Rendering) — данные на сервере:

  • Сервер получает запрос, делает fetch данных
  • Рендерит HTML с данными, отправляет браузеру
  • Пользователь видит контент сразу
  • Хорошо для SEO, но каждый запрос = новый рендер
  • SSG (Static Site Generation) — генерация при сборке:

  • При сборке проекта все страницы рендерятся в HTML
  • Файлы отдаются с CDN — максимально быстро
  • Подходит для блогов, документации, лендингов
  • // Pages Router (устаревший, но важно знать)
    
    // SSR: данные на каждый запрос
    export async function getServerSideProps(context) {
      const user = await fetchUser(context.params.id)
      return { props: { user } }
    }
    
    // SSG: данные при сборке
    export async function getStaticProps() {
      const posts = await fetchPosts()
      return { props: { posts }, revalidate: 60 }  // ISR: обновление каждые 60с
    }

    Когда использовать Next.js

    Используйте Next.js когда нужны:

  • SEO (блог, лендинг, e-commerce)
  • Быстрая первоначальная загрузка
  • Серверная логика рядом с UI
  • Простой деплой (Vercel)
  • Используйте plain React (Vite) когда:

  • Внутренние дашборды (SEO не нужен)
  • SPA без серверной части
  • Обучение, прототипирование
  • Примеры

    Сравнение CSR vs SSR: клиентский рендеринг с fetch в useEffect против серверного рендеринга с данными сразу в пропсах

    // Показываем разницу между CSR и SSR на уровне JavaScript.
    // Симулируем задержку сети и разные стратегии рендеринга.
    
    // --- Симуляция API ---
    
    const fakeDB = {
      users: [
        { id: 1, name: 'Алексей Иванов', role: 'Разработчик', city: 'Москва' },
        { id: 2, name: 'Мария Петрова', role: 'Дизайнер', city: 'СПб' },
      ]
    }
    
    function simulateApiCall(userId, delay = 500) {
      return new Promise(resolve => {
        setTimeout(() => {
          resolve(fakeDB.users.find(u => u.id === userId))
        }, delay)
      })
    }
    
    // --- CSR: Client-Side Rendering ---
    // (как React без Next.js, данные через useEffect + fetch)
    
    async function renderCSR(userId) {
      console.log('=== CSR (Client-Side Rendering) ===')
    
      const startTime = performance.now()
    
      // 1. Браузер получает пустой HTML сразу
      const ms = () => Math.round(performance.now() - startTime)
      console.log('[' + ms() + 'мс] HTML получен: <div id="root"></div>')
      console.log('[' + ms() + 'мс] Загрузка JS бандла...')
    
      // Симуляция загрузки JS
      await new Promise(r => setTimeout(r, 200))
      console.log('[' + ms() + 'мс] JS загружен, React монтируется')
      console.log('[' + ms() + 'мс] Пользователь видит: [ЗАГРУЗКА...]')
    
      // useEffect запускается после монтирования
      console.log('[' + ms() + 'мс] useEffect: запускаем fetch...')
      const user = await simulateApiCall(userId, 500)
    
      console.log('[' + ms() + 'мс] Данные получены, ре-рендер')
      console.log('[' + ms() + 'мс] Пользователь видит: ' + user.name + ', ' + user.role)
    
      return {
        totalTime: Math.round(performance.now() - startTime),
        strategy: 'CSR',
        contentVisibleAt: 700  // мс до появления контента
      }
    }
    
    // --- SSR: Server-Side Rendering ---
    // (как Next.js getServerSideProps или Server Components)
    
    async function renderSSR(userId) {
      console.log('
    === SSR (Server-Side Rendering) ===')
    
      const startTime = performance.now()
    
      // 1. Сервер получает запрос и СРАЗУ делает fetch
      const ms2 = () => Math.round(performance.now() - startTime)
      console.log('[' + ms2() + 'мс] Сервер получил запрос')
      console.log('[' + ms2() + 'мс] Сервер делает запрос к БД...')
    
      const user = await simulateApiCall(userId, 100)  // БД на том же сервере — быстрее!
    
      console.log('[' + ms2() + 'мс] БД ответила, рендерим HTML')
    
      // HTML уже содержит данные!
      const html = '<div class="profile"><h1>' + user.name + '</h1><p>' + user.role + ' • ' + user.city + '</p></div>'
    
      console.log('[' + ms2() + 'мс] HTML с данными отправлен браузеру')
      console.log('[' + ms2() + 'мс] Пользователь СРАЗУ видит: ' + user.name + ', ' + user.role)
    
      // Next.js Server Component (App Router):
      // async function UserProfile({ id }) {
      //   const user = await db.users.findById(id)  // прямо к БД!
      //   return <div><h1>{user.name}</h1></div>
      // }
    
      return {
        totalTime: Math.round(performance.now() - startTime),
        strategy: 'SSR',
        contentVisibleAt: 150
      }
    }
    
    // --- Запуск и сравнение ---
    
    async function compare() {
      const csrResult = await renderCSR(1)
      const ssrResult = await renderSSR(1)
    
      console.log('
    === Итоги ===')
      console.log('CSR: контент появился через ~' + csrResult.contentVisibleAt + 'мс')
      console.log('SSR: контент появился через ~' + ssrResult.contentVisibleAt + 'мс')
      console.log('SSR быстрее для первой загрузки на ~' + (csrResult.contentVisibleAt - ssrResult.contentVisibleAt) + 'мс')
      console.log('
    CSR лучше для: дашборды, SPA, авторизованные страницы')
      console.log('SSR лучше для: лендинги, блоги, e-commerce (SEO + скорость)')
    }
    
    compare()

    Введение в Next.js

    Что такое Next.js

    Next.js — это React-фреймворк от Vercel. Если React — это движок, то Next.js — это готовый автомобиль: маршрутизация из коробки, серверный рендеринг, оптимизация изображений, API-маршруты и многое другое.

    Что добавляет Next.js к React:

  • Файловая маршрутизация (папки = маршруты)
  • Server-Side Rendering (SSR) и Static Generation (SSG)
  • Server Components (React код на сервере)
  • API Routes (бэкенд прямо в проекте)
  • Оптимизация изображений (next/image)
  • Оптимизация шрифтов (next/font)
  • Деплой в один клик на Vercel
  • App Router vs Pages Router

    Next.js имеет два роутера:

    | | Pages Router | App Router (актуальный) |

    |---|---|---|

    | Папка | /pages | /app |

    | Данные | getServerSideProps / getStaticProps | async Server Components |

    | Компоненты | Всё клиентское | Server Components по умолчанию |

    | Версия | До Next.js 12 | Next.js 13+ (рекомендуется) |

    Server Components vs Client Components

    Ключевое нововведение Next.js 13+ (App Router):

    // Server Component (по умолчанию в /app) — рендерится на сервере
    // Может: async/await, обращаться к БД напрямую, читать файлы
    // Не может: useState, useEffect, обработчики событий, browser API
    
    async function UserProfile({ id }) {
      // Прямой запрос к БД — без fetch, без useEffect, без loading state!
      const user = await db.users.findById(id)
    
      return (
        <div>
          <h1>{user.name}</h1>
          <InteractiveButton userId={id} />  {/* клиентский компонент */}
        </div>
      )
    }
    
    // Client Component — 'use client' директива вверху файла
    'use client'
    function InteractiveButton({ userId }) {
      const [liked, setLiked] = useState(false)  // useState — только в клиентском!
    
      return <button onClick={() => setLiked(!liked)}>
        {liked ? 'Понравилось' : 'Нравится'}
      </button>
    }

    Файловая маршрутизация в App Router

    app/
      page.tsx           -> /
      about/
        page.tsx         -> /about
      blog/
        page.tsx         -> /blog
        [slug]/
          page.tsx       -> /blog/любой-slug
      dashboard/
        layout.tsx       -> общий layout для /dashboard/*
        page.tsx         -> /dashboard
        settings/
          page.tsx       -> /dashboard/settings
    // app/blog/[slug]/page.tsx
    export default async function BlogPost({ params }) {
      const { slug } = params  // URL параметр из папки [slug]
      const post = await fetchPost(slug)
    
      return <article>{post.content}</article>
    }
    
    // Статическая генерация: список всех возможных slug
    export async function generateStaticParams() {
      const posts = await fetchAllPosts()
      return posts.map(post => ({ slug: post.slug }))
    }

    Сравнение: CSR vs SSR vs SSG

    CSR (Client-Side Rendering) — React по умолчанию:

  • Браузер загружает пустой HTML + JS бандл
  • JS выполняется, компонент монтируется, делает fetch
  • Пользователь видит лоадер, потом данные
  • Плохо для SEO (боты видят пустой HTML)
  • SSR (Server-Side Rendering) — данные на сервере:

  • Сервер получает запрос, делает fetch данных
  • Рендерит HTML с данными, отправляет браузеру
  • Пользователь видит контент сразу
  • Хорошо для SEO, но каждый запрос = новый рендер
  • SSG (Static Site Generation) — генерация при сборке:

  • При сборке проекта все страницы рендерятся в HTML
  • Файлы отдаются с CDN — максимально быстро
  • Подходит для блогов, документации, лендингов
  • // Pages Router (устаревший, но важно знать)
    
    // SSR: данные на каждый запрос
    export async function getServerSideProps(context) {
      const user = await fetchUser(context.params.id)
      return { props: { user } }
    }
    
    // SSG: данные при сборке
    export async function getStaticProps() {
      const posts = await fetchPosts()
      return { props: { posts }, revalidate: 60 }  // ISR: обновление каждые 60с
    }

    Когда использовать Next.js

    Используйте Next.js когда нужны:

  • SEO (блог, лендинг, e-commerce)
  • Быстрая первоначальная загрузка
  • Серверная логика рядом с UI
  • Простой деплой (Vercel)
  • Используйте plain React (Vite) когда:

  • Внутренние дашборды (SEO не нужен)
  • SPA без серверной части
  • Обучение, прототипирование
  • Примеры

    Сравнение CSR vs SSR: клиентский рендеринг с fetch в useEffect против серверного рендеринга с данными сразу в пропсах

    // Показываем разницу между CSR и SSR на уровне JavaScript.
    // Симулируем задержку сети и разные стратегии рендеринга.
    
    // --- Симуляция API ---
    
    const fakeDB = {
      users: [
        { id: 1, name: 'Алексей Иванов', role: 'Разработчик', city: 'Москва' },
        { id: 2, name: 'Мария Петрова', role: 'Дизайнер', city: 'СПб' },
      ]
    }
    
    function simulateApiCall(userId, delay = 500) {
      return new Promise(resolve => {
        setTimeout(() => {
          resolve(fakeDB.users.find(u => u.id === userId))
        }, delay)
      })
    }
    
    // --- CSR: Client-Side Rendering ---
    // (как React без Next.js, данные через useEffect + fetch)
    
    async function renderCSR(userId) {
      console.log('=== CSR (Client-Side Rendering) ===')
    
      const startTime = performance.now()
    
      // 1. Браузер получает пустой HTML сразу
      const ms = () => Math.round(performance.now() - startTime)
      console.log('[' + ms() + 'мс] HTML получен: <div id="root"></div>')
      console.log('[' + ms() + 'мс] Загрузка JS бандла...')
    
      // Симуляция загрузки JS
      await new Promise(r => setTimeout(r, 200))
      console.log('[' + ms() + 'мс] JS загружен, React монтируется')
      console.log('[' + ms() + 'мс] Пользователь видит: [ЗАГРУЗКА...]')
    
      // useEffect запускается после монтирования
      console.log('[' + ms() + 'мс] useEffect: запускаем fetch...')
      const user = await simulateApiCall(userId, 500)
    
      console.log('[' + ms() + 'мс] Данные получены, ре-рендер')
      console.log('[' + ms() + 'мс] Пользователь видит: ' + user.name + ', ' + user.role)
    
      return {
        totalTime: Math.round(performance.now() - startTime),
        strategy: 'CSR',
        contentVisibleAt: 700  // мс до появления контента
      }
    }
    
    // --- SSR: Server-Side Rendering ---
    // (как Next.js getServerSideProps или Server Components)
    
    async function renderSSR(userId) {
      console.log('
    === SSR (Server-Side Rendering) ===')
    
      const startTime = performance.now()
    
      // 1. Сервер получает запрос и СРАЗУ делает fetch
      const ms2 = () => Math.round(performance.now() - startTime)
      console.log('[' + ms2() + 'мс] Сервер получил запрос')
      console.log('[' + ms2() + 'мс] Сервер делает запрос к БД...')
    
      const user = await simulateApiCall(userId, 100)  // БД на том же сервере — быстрее!
    
      console.log('[' + ms2() + 'мс] БД ответила, рендерим HTML')
    
      // HTML уже содержит данные!
      const html = '<div class="profile"><h1>' + user.name + '</h1><p>' + user.role + ' • ' + user.city + '</p></div>'
    
      console.log('[' + ms2() + 'мс] HTML с данными отправлен браузеру')
      console.log('[' + ms2() + 'мс] Пользователь СРАЗУ видит: ' + user.name + ', ' + user.role)
    
      // Next.js Server Component (App Router):
      // async function UserProfile({ id }) {
      //   const user = await db.users.findById(id)  // прямо к БД!
      //   return <div><h1>{user.name}</h1></div>
      // }
    
      return {
        totalTime: Math.round(performance.now() - startTime),
        strategy: 'SSR',
        contentVisibleAt: 150
      }
    }
    
    // --- Запуск и сравнение ---
    
    async function compare() {
      const csrResult = await renderCSR(1)
      const ssrResult = await renderSSR(1)
    
      console.log('
    === Итоги ===')
      console.log('CSR: контент появился через ~' + csrResult.contentVisibleAt + 'мс')
      console.log('SSR: контент появился через ~' + ssrResult.contentVisibleAt + 'мс')
      console.log('SSR быстрее для первой загрузки на ~' + (csrResult.contentVisibleAt - ssrResult.contentVisibleAt) + 'мс')
      console.log('
    CSR лучше для: дашборды, SPA, авторизованные страницы')
      console.log('SSR лучше для: лендинги, блоги, e-commerce (SEO + скорость)')
    }
    
    compare()

    Задание

    Создай компонент App имитирующий Next.js страницу с загрузкой данных. Используй useEffect для "получения" данных при монтировании (имитируй задержку через setTimeout). Пока данные загружаются — показывай спиннер, после — карточки постов. Данные хардкодь прямо в компоненте как константу.

    Подсказка

    useState(null) для начального состояния. setTimeout с 1500мс имитирует загрузку. setPosts(MOCK_POSTS) и setLoading(false) в колбэке. Условие загрузки: if (loading). Рендер списка: posts.map(post => <PostCard key={post.id} ... />).

    Загружаем среду выполнения...
    Загружаем AI-помощника...