Перейти до вмісту

post.md

23 жовт. 2026 р. · 3 хв читання
Анімований ASCII-арт: катамаран Nacra 17 пливе океаном на заході сонця

Як побудовано цей сайт

Коротка екскурсія архітектурою cmkrw.com: від терміналоподібного макета до Markdown, діаграм і звітів про помилки.

  • nextjs
  • design
Зміст

Цей сайт навмисно невеликий: одна сторінка, один блог і якомога менше JavaScript. Ось як усе влаштовано — якщо захочете зробити щось подібне.

Термінал — це і є макет

Сайт побудовано як термінальний застосунок. Угорі — панель вкладок, унизу — рядок стану, а все між ними — панелі: рамки із заголовком, вбудованим у верхню межу. Усі панелі малює один невеликий компонент Pane.

Макет блогу: список дописів ліворуч і відкритий допис праворучБлог у вигляді «список — вміст»: список залишається на місці, поки змінюється допис.

Навігація слідує тій самій ідеї. У блозі список дописів залишається ліворуч, а допис відкривається праворуч, тож для переходу між дописами не потрібне посилання «назад». Комбінації клавіш працюють так само, як у терміналі, а цю таблицю можна відсортувати клацанням по заголовку стовпця:

комбінації клавіш
1Відкрити головнуусюди
2Відкрити блогусюди
jПрокрутити внизусюди
kПрокрутити вгоруусюди
g gНа початокусюди
GУ кінецьусюди
nСтаріший дописблог
pНовіший дописблог
dЗмінити темуусюди
?Показати комбінаціїусюди

Анімація — переважно CSS. Команда в шапці друкується анімацією steps(), панелі випливають під час прокручування, а лінія під панеллю вкладок і відсоток у рядку стану стежать за позицією прокручування. За prefers-reduced-motion усе це вимикається.

Як пишуться дописи

Дописи — це звичайні Markdown-файли в content/blog/<slug>/<locale>.md, які відображає TanStack Markdown. Якщо перекладу немає, використовується англійський файл, а draft: true не пускає допис у продакшен-збірку.

Решту роблять три інструменти TanStack:

  • Markdown розбирає допис і відображає його як елементи React — без проміжного HTML-рядка.
  • Highlight розфарбовує блоки коду. До збірки потрапляють лише зареєстровані мною мови, а кольори беруться з тих самих CSS-змінних, що й уся тема.
  • Charts і Table вбудовуються однорядковим коментарем з іменем JSON-файлу поруч із дописом.

Блоки коду можуть мати заголовок з іменем файлу:

src/features/blog/embeds/index.tsx
export function createEmbeds(slug: string, locale: string) {
  return {
    chart: ({ data = "", kind = "bar", x = "x", y = "y", title = "Chart" }) => (
      <TuiChart
        rows={getPostDataset(slug, data, locale)}
        kind={kind}
        x={x}
        y={y}
        label={title}
      />
    ),
  }
}

Звичайні таблиці Markdown теж працюють і отримують той самий стиль:

ШарТекаВідповідальність
Маршрутиapp/Сторінки, метадані, значки та зображення
Інтерфейсcomponents/tui/Панелі, верхня панель, рядок стану, прив'язки клавіш
Функціїfeatures/Розділи головної сторінки та блог
Утилітиlib/, hooks/Метадані, спостережуваність, введення з клавіатури

Ось скільки файлів вихідного коду лежить у кожній теці на момент написання:

Файли вихідного коду за теками
Показати дані
folderfiles
app17
components16
lib16
features15
i18n4
hooks3
types1

Згенеровані значки та картки для соцмереж

Next.js 16 створює зображення метаданих із файлів у каталозі app/:

  • icon.tsx і apple-icon.tsx малюють фавікон і значок для iOS з одного SVG — assets/logo-icon.svg.
  • opengraph-image.tsx малює картку головної сторінки, а ще один файл — картку для кожного допису.

Файла favicon.tsx немає. У документації прямо сказано, що фавікон згенерувати не можна, тому цю роботу виконує icon.tsx. Усе це спирається на невеликий спільний набір примітивів, тож зміна знака бренду змінює фавікон і всі картки одразу.

Інструментування

За спостережуваність відповідають два файли. instrumentation.ts працює на сервері й повідомляє про помилки запитів. instrumentation-client.ts запускається до гідратації та відстежує помилки клієнта, вебметрики й зміну маршрутів. Обидва передають події репортеру, який розсилає їх одержувачам:

src/lib/observability/server.ts
export const serverReporter = createReporter([
  consoleSink,
  ...(endpoint ? [endpointSink(endpoint)] : []),
])

Події клієнта надсилаються на /api/observability, де їх записують у журнал. Щоб згодом підключити справжній збирач, досить додати одного одержувача, не чіпаючи жоден із вхідних файлів.

Переклади

Сайт доступний десятьма мовами, зокрема арабською та івритом, які читаються справа наліво. Усі рядки лежать у файлі повідомлень для кожної мови; якщо ключа раптом бракує, знизу підмішуються повідомлення мови за замовчуванням, тож сторінка ніколи не лишається порожньою. Дописи працюють так само: у кожної мови свій файл, а за його відсутності використовується англійський.

NORMAL~/blog/how-this-site-is-built
0%

Комбінації клавіш

12
Основна навігація
jk
Прокрутка вниз / вгору
g gG
На початок / у кінець
np
Новіший / старіший допис
d
Змінити тему
:
Відкрити палітру команд
?
Показати або сховати довідку
esc