Skip to content
Personal2026

Этот сайт

Сайт-резюме на Next.js и Payload CMS: весь контент редактируется в админке, стаж и метрики считаются из данных и CI.

Context and goal

Сайт-резюме должен за полминуты отвечать рекрутеру на вопросы «кто, какой стек, как связаться», а техлиду — показывать, как я принимаю решения. Заодно он сам служит кейсом: всё, что на нём сказано про производительность, доступность и тесты, проверяется на нём же.

Контент полностью редактируется в админке. Пустые поля не превращаются в заглушки: блок без данных просто не рендерится. Цифры берутся только из данных или из CI.

My role

Дизайн, фронтенд, бэкенд и инфраструктура — от модели данных до деплоя.

  • Next.js 16 (App Router, React Server Components) и Payload CMS 3 в одном приложении, PostgreSQL, миграции.
  • Архитектура по Feature-Sliced Design, границы слоёв проверяет Steiger в CI.
  • Дизайн-система на CSS custom properties и tailwind-variants, витрина в Storybook с проверкой доступности.
  • Анимации на GSAP и Motion с общим провайдером prefers-reduced-motion.
  • PDF-резюме из тех же данных через @react-pdf/renderer.
  • Docker, Caddy с автоматическим HTTPS, GitHub Actions с Lighthouse CI.

Decisions

Стаж считается из записей опыта. Периоды переводятся в месяцы, пересекающиеся склеиваются, поэтому параллельная работа не удваивает стаж. Одна и та же функция работает на сайте и в PDF, и цифры не расходятся.

src/shared/lib/date/periods.ts
export const mergeRanges = (ranges: MonthRange[]): MonthRange[] => {
  const sorted = [...ranges].sort((a, b) => a.from - b.from)
  const merged: MonthRange[] = []
  for (const range of sorted) {
    const last = merged.at(-1)
    if (last && range.from <= last.to + 1) {
      last.to = Math.max(last.to, range.to)
    } else {
      merged.push({ ...range })
    }
  }
  return merged
}

Объединение периодов: смежные и пересекающиеся интервалы склеиваются до подсчёта.

Русская версия живёт в корне, английская — под /en. Внутри это один динамический сегмент [locale]: proxy переписывает адрес и на каждый запрос выдаёт nonce для Content Security Policy.

src/proxy.ts
const response = hasLocale
  ? NextResponse.next({ request: { headers: requestHeaders } })
  : NextResponse.rewrite(
      new URL(`/${defaultLocale}${pathname === '/' ? '' : pathname}${search}`, request.url),
      { request: { headers: requestHeaders } },
    )

response.headers.set('Content-Security-Policy', csp)

Из-за nonce страницы рендерятся динамически, но данные кэшируются по тегам. Хуки Payload после сохранения сбрасывают только затронутые теги, поэтому правка в админке видна на сайте без пересборки.

Навык показывается, только если на него ссылается проект или место работы. Неподтверждённые навыки и всё, что скрыто из-за пустых полей, собраны на дашборде админки.

Results

Производительность, главная
72из 100
Локальный прогон Lighthouse CI 22.10.2026, мобильный профиль (Moto G Power, медленный 4G), медиана трёх замеров.
Производительность, /projects
55из 100
Локальный прогон Lighthouse CI 22.10.2026, мобильный профиль (Moto G Power, медленный 4G), медиана трёх замеров. На этой странице больше всего разброс между замерами.
Производительность, этот кейс
74из 100
Локальный прогон Lighthouse CI 22.10.2026, мобильный профиль (Moto G Power, медленный 4G), медиана трёх замеров.
Доступность, лучшие практики, SEO
100из 100
На главной, /projects и этом кейсе. Локальный прогон Lighthouse CI 22.10.2026, мобильный профиль (Moto G Power, медленный 4G), медиана трёх замеров.
LCP главной
3,7с
Локальный прогон Lighthouse CI 22.10.2026, мобильный профиль (Moto G Power, медленный 4G), медиана трёх замеров. CLS на всех трёх страницах — 0.
Total Blocking Time главной
450мс
Локальный прогон Lighthouse CI 22.10.2026, мобильный профиль (Moto G Power, медленный 4G), медиана трёх замеров.
Начальный JS главной
172КБ gzip
Все скрипты, загруженные до затишья сети, в том же локальном прогоне 22.10.2026. Форма связи, Motion, GSAP и WebGL грузятся отдельными чанками по необходимости.
Автотесты
119 + 55
Unit- и компонентные тесты Vitest плюс прогоны e2e Playwright на десктопном и мобильном Chromium (включая axe в обеих темах), 22.10.2026.