Вивчай
Домашнє завдання #24 · Next.js: App Router, SSG, MDX
100 балів+20 бонусintermediate

Домашнє завдання #24: Довідник твого продукту

Побудуй статичний сайт на Next.js App Router: статті лежать як MDX-файли, сторінки генеруються під час білду (SSG), а маршрути народжуються з файлової структури. Це рівно та архітектура, на якій працює сайт цього курсу.

Але писати «5 абстрактних статей про Flexbox» — нудно. Тому обери, про що буде твій контент:

  • Довідник твого продукту (з ДЗ #5): для фільмотеки — «Гід по жанрах», «Як обрати, що дивитися ввечері», «5 режисерів, з яких варто почати»; для рецептів — гіди по кухнях; для ігор — по жанрах і платформах
  • Щоденник курсу: 5 чесних нотаток «що я вивчив» — про перший git push, про день, коли клацнув Flexbox, про баг, який ти шукав годину
Порада

Пиши по-справжньому. Ці сторінки переїдуть у твоє портфоліо в ДЗ #26 — їх читатимуть живі люди, можливо навіть твій майбутній роботодавець.


Підготовка

npx create-next-app@latest my-guide --typescript --tailwind --app
cd my-guide
npm install gray-matter

Структура контенту (назви файлів — під твої теми):

content/
  posts/
    gid-po-zhanrah.mdx
    shcho-dyvytysia-vvecheri.mdx
    ...

Frontmatter кожного файлу:

---
title: "Гід по жанрах: з чого почати"
date: "2026-02-01"
description: "Короткий опис для списку та SEO"
tags: ["гід", "жанри"]
---

Завдання

1. Утиліти для контенту

Файл src/lib/posts.ts з функціями (читання файлів — fs, парсинг frontmatter — gray-matter):

  • getAllPosts() — масив усіх постів (meta + slug), нові першими
  • getPostBySlug(slug) — один пост (meta + content)
  • getAllTags() — масив унікальних тегів

2. Список статей (/blog)

  • Server Component: усі статті з заголовком, датою, описом і тегами
  • Кожна — посилання на свою сторінку

3. Сторінка статті (/blog/[slug])

  • Повний контент, заголовок, дата, теги
  • generateStaticParams — Next.js має згенерувати всі сторінки статично під час білду
  • Посилання «Назад до всіх статей»
  • Неіснуючий slug → 404 (notFound())

4. Фільтрація за тегами (/blog/tag/[tag])

  • Сторінка зі статтями конкретного тега, заголовок «Статті з тегом: ...»
  • generateStaticParams на основі getAllTags()
  • Теги у списках і статтях — клікабельні посилання сюди

5. Layout та навігація

  • Кореневий layout.tsx: header (назва-бренд + навігація) і footer
  • Навігація: Головна, Статті, Про автора
  • /about — сторінка про тебе (2-3 абзаци, хто ти і що це за сайт)

6. SEO metadata

  • Статичний metadata для головної, списку та about
  • generateMetadata для сторінок статей: title і description з frontmatter
  • Open Graph поля (openGraph.title, openGraph.description)

Вимоги до контенту

Мінімум 5 статей, кожна — від 3-5 абзаців власного тексту. Не lorem ipsum і не згенерована вода: конкретика, приклади, твоя думка. У статей мають бути перехресні теги (хоча б один тег зустрічається у 2+ статтях — інакше сторінки тегів не мають сенсу).


Як здати

  1. Це — окремий публічний репозиторій
  2. README.md: про що сайт, як запустити (npm install, npm run dev, npm run build); env-змінних тут немає — так і напиши
  3. Мінімум 5 комітів — наприклад: setup, утиліти, список і сторінка статті, теги, metadata + контент
  4. npm run build проходить без помилок — це головний тест SSG
  5. Скріншоти в screenshots/: desktop.png (список статей), mobile.png, interaction.png (відкрита стаття або сторінка тега)
  6. Здай посиланням на репозиторій

Критерії оцінювання

КритерійТипБали
lib/posts.ts: getAllPosts із сортуванням за датою, getPostBySlug, getAllTags[код]15
Список статей: Server Component, заголовок/дата/опис/теги, робочі посилання[поведінка]15
Сторінка статті: generateStaticParams, повний контент, 404 для неіснуючого slug[код]20
Сторінки тегів: свій generateStaticParams, клікабельні теги ведуть сюди[поведінка]10
Layout: header з навігацією, footer, сторінка /about[код]10
SEO: статичний metadata + generateMetadata для статей + Open Graph[код]10
5+ справжніх статей по 3-5 абзаців, перехресні теги[код]5
Скріншоти: список, читабельна сторінка статті, mobile[скрін]10
Якість коду[код]5
Разом100
Бонус: час читання для кожної статті (порахований зі слів)[код]+5
Бонус: навігація «попередня / наступна стаття» на сторінці посту[поведінка]+5
Бонус: пошук за заголовком (Client Component) або RSS public/rss.xml[поведінка]+10

Підказки

Рівень 1: напрямок

Усе крутиться навколо lib/posts.ts — напиши і перевір його першим (можна тимчасово console.log у page.tsx). Сторінки в App Router — серверні за замовчуванням, тому вони можуть просто викликати твої функції з fs напряму, без жодного API. Динамічний маршрут — папка у квадратних дужках: app/blog/[slug]/page.tsx.

Рівень 2: які інструменти

fs.readdirSync + fs.readFileSync + matter(fileContent) дає { data, content }. Slug — ім'я файлу без .mdx. У сторінці [slug]: export async function generateStaticParams() повертає [{ slug: '...' }, ...]; сам компонент отримує params (в нових версіях Next це Promise — await params). generateMetadata — експортована функція поруч з компонентом, теж отримує params. Не знайшли пост — notFound() з next/navigation. Рендер markdown-тексту: почни з простого (розбий content на абзаци) або підключи next-mdx-remote.

Рівень 3: скелет сторінки статті
  1. app/blog/[slug]/page.tsx: generateStaticParamsgetAllPosts().map(p => ({ slug: p.slug }))
  2. generateMetadata({ params })await params, взяти пост → { title, description, openGraph: {...} }
  3. Компонент: const { slug } = await params; const post = getPostBySlug(slug); if (!post) notFound();
  4. Розмітка: h1 з title, дата (toLocaleDateString('uk-UA')), теги як Link на /blog/tag/..., контент
  5. Сторінка тега аналогічна: generateStaticParams з getAllTags(), контент — getAllPosts().filter(p => p.tags.includes(tag))
  6. Фінальна перевірка: npm run build — у виводі побачиш (SSG) навпроти своїх маршрутів

Що далі

Не видаляй ці статті! У ДЗ #26 весь цей розділ переїде у твоє портфоліо як живий блог — а перед тим, у ДЗ #25, твій каталог отримає власний бекенд на API routes.