Files
homepage/spec/migration.md

16 KiB
Raw Blame History

План миграции сайта vakhrushev.me с Sculpin на Astro

Обзор текущего состояния

Стек

  • Генератор: Sculpin 3 (PHP 8)
  • Шаблоны: Twig (base → internal → article, три уровня наследования)
  • Фронтенд: Webpack 4, SCSS, Vue 2 (интерактивная демка гадалки)
  • Сборка: Docker (PHP + Node контейнеры), Taskfile
  • Деплой: Docker-образ nginx, Ansible на личный сервер
  • Аналитика: Яндекс.Метрика
  • Шрифты: Google Fonts (PT Serif, Source Code Pro), Font Awesome 5
  • Кастомные бандлы Sculpin: HtmlPrettier, SiteMap, TwigExtension (hashed_asset)

Контент

  • 8 статей в markdown (2019-2020), русский язык
  • 1 статья с интерактивным Vue-компонентом (гадалка Шеннона)
  • Страницы: главная, список статей, 404
  • Atom-лента (ручной шаблон)
  • Sitemap (кастомный бандл + шаблон)
  • robots.txt

Структура стилей

  • Переменные SCSS: шрифт PT Serif 20px, ширина 740px, цвета GitHub
  • Базовые стили, стили главной, стили внутренних страниц, навигация
  • BEM-подобная методология

Принятые решения

  • URL статей: формат /articles/2019-05-01-predictor/, slug = имя файла без расширения
  • Пакет @anwinged/predictor: чистый TypeScript, без привязки к фреймворку — миграция на Vue 3 затрагивает только обёртку
  • Tailwind CSS: используем; Tailwind 4 делает tree-shaking из коробки — в сборку попадают только используемые утилиты
  • Текст на главной: переносим как есть, автор обновит сам
  • Галерея: один альбом с фотографиями, хранение в src/content/gallery/ (оптимизация через Astro Image)
  • Локальная разработка: всё через Docker (и dev, и build), npm на хосте не используем

Целевое состояние

Стек

  • Генератор: Astro
  • Стилизация: Tailwind CSS 4
  • Интерактивность: Vue 3 (islands, для гадалки и будущих компонентов)
  • Сборка: Docker (один Node-контейнер), Taskfile
  • Деплой: Docker-образ nginx, Ansible (без изменений)

Разделы сайта

  1. Главная — краткое описание, ссылки
  2. Блог — статьи и эссе по программированию
  3. Галерея — один альбом с избранными фотографиями

Задачи

Этап 1. Инициализация проекта Astro

1.1. Создать проект Astro

  • Инициализировать Astro в корне проекта (или в отдельной ветке)
  • Настроить astro.config.mjs: site URL https://vakhrushev.me, язык ru
  • Установить интеграции: @astrojs/sitemap, @astrojs/rss, @astrojs/vue, @astrojs/tailwind
  • Настроить TypeScript (strict или relaxed — на усмотрение)

1.2. Настроить Tailwind CSS 4

  • Установить и настроить Tailwind 4 (tree-shaking из коробки — в сборку попадает только используемый CSS)
  • Перенести дизайн-токены из SCSS-переменных в Tailwind-конфигурацию (через @theme в CSS):
    • Шрифты: PT Serif (serif), Source Code Pro (mono)
    • Цвета: #24292e (текст), #0366d6 (ссылки), #e6e6e6 (линии)
    • Ширина контента: 740px
    • Размер шрифта: 20px базовый

1.3. Настроить Docker для сборки

  • Заменить два Docker-образа (PHP + Node) одним Node-образом
    • Использовать актуальную LTS-версию Node (22)
  • Удалить PHP Dockerfile — он больше не нужен
  • Сохранить Dockerfile.nginx.prod, обновить путь: dist/ вместо output_prod/

1.4. Обновить Taskfile

  • Убрать задачи: composer, sculpin, shell-node, format-php
  • Обновить задачи сборки:
    • build-prod: astro build
  • Добавить задачу dev: запуск astro dev для локальной разработки (через Docker с проброшенными портами)
  • Обновить задачу deploy: путь к собранным файлам dist/
  • Сохранить структуру Docker-команд через Taskfile
  • Все npm/astro-команды выполняются внутри Docker-контейнера

Этап 2. Лейауты и компоненты

2.1. Создать базовый лейаут BaseLayout.astro

Аналог текущего base.html.twig. Должен включать:

  • <html lang="ru">, мета-теги (charset, viewport, description, keywords)
  • Open Graph мета-теги (og:site_name, og:title, og:description, og:url, og:locale)
  • Подключение шрифтов Google Fonts (PT Serif, Source Code Pro)
  • Яндекс.Метрика (вынести в отдельный компонент YandexMetrika.astro)
  • Yandex verification мета-тег
  • Слот для контента

Параметры (props): title, description, keywords

2.2. Создать лейаут для внутренних страниц InternalLayout.astro

Аналог internal.html.twig:

  • Использует BaseLayout
  • Навигация (компонент Navigation.astro)
  • Контейнер .page с ограничением ширины

2.3. Создать лейаут для статей ArticleLayout.astro

Аналог article.html.twig:

  • Использует InternalLayout
  • Заголовок статьи <h1>
  • Дата публикации и email внизу
  • Слот для содержимого статьи

2.4. Создать общие компоненты

  • Navigation.astro — навигация (Главная, Блог, Галерея)
  • YandexMetrika.astro — код счетчика аналитики
  • ArticleList.astro — список статей, сгруппированных по годам

Особенность: Font Awesome больше не нужен (социальные ссылки удалены в коммите 63a324c). Если понадобятся иконки — использовать astro-icon или inline SVG.


Этап 3. Content Collections и перенос статей

3.1. Настроить Content Collection для статей

  • Создать src/content/articles/ для markdown-файлов
  • Определить схему в src/content.config.ts:
    {
      title: z.string(),
      description: z.string().optional(),
      keywords: z.array(z.string()).optional(),
      tags: z.array(z.string()).optional(),
      draft: z.boolean().default(false),
      // date — парсится из имени файла (YYYY-MM-DD-slug.md), не хранится в frontmatter
    }
    

3.2. Перенести статьи

Перенести 8 markdown-файлов из source/_articles/ в src/content/articles/.

Имена файлов сохраняют даты (формат YYYY-MM-DD-slug.md) для удобной навигации в файловой системе. Дата парсится из имени файла при сборке (в content.config.ts или в getStaticPaths), дублировать в frontmatter не нужно.

Необходимые изменения в каждом файле:

  • Удалить Sculpin-специфичные поля frontmatter (layout, styles, scripts, use)
  • Оставить title, description, keywords
  • Опционально: добавить tags для будущей фильтрации

Список статей:

  1. 2019-05-01-predictor.mdОСОБЫЙ СЛУЧАЙ, см. задачу 3.3
  2. 2019-06-01-php-serialization.md
  3. 2019-06-28-storytelling.md
  4. 2019-08-08-yandex-disk-image-hosting.md
  5. 2019-09-26-highload-videos.md
  6. 2020-06-27-interesting-programming-blogs.md
  7. 2020-06-27-type-discriminant.md
  8. 2020-11-08-nullable-fields.md

3.3. Перенести статью с интерактивным компонентом (гадалка)

Статья predictor.md содержит встроенный Vue-компонент <div id="app"></div>.

Шаги:

  • Переименовать файл в .mdx
  • Обновить Vue-компонент PredictorDemo.vue до Vue 3 (Composition API или Options API)
    • @anwinged/predictor — чистый TS, совместимость с Vue 3 не затронута
    • Перенести scoped-стили компонента на Tailwind или оставить scoped CSS
  • В MDX-файле: импортировать компонент и использовать <PredictorDemo client:visible />
  • Удалить ручное подключение JS/CSS через frontmatter (styles, scripts)

Этап 4. Страницы

4.1. Главная страница src/pages/index.astro

  • Использовать BaseLayout
  • Имя, краткое описание (обновить текст — актуализировать информацию о себе)
  • Ссылки: email, git, другие
  • Список последних статей (через getCollection('articles'))

4.2. Страница списка статей src/pages/articles/index.astro

  • Использовать InternalLayout
  • Список всех статей, сгруппированных по годам (как сейчас в article_list.twig)
  • Сортировка по дате, от новых к старым

4.3. Динамические страницы статей src/pages/articles/[...slug].astro

  • Использовать ArticleLayout
  • Рендеринг markdown/MDX содержимого через render()
  • SEO мета-теги из frontmatter

4.4. Страница 404 src/pages/404.astro

  • Использовать InternalLayout
  • Текст "Страница не найдена" и ссылка на главную

4.5. Страница галереи src/pages/gallery/index.astro

  • Использовать InternalLayout
  • Content Collection gallery — фотографии с метаданными (title, описание, дата, порядок)
  • Структура хранения:
    src/content/gallery/
    ├── photo-001.md        # frontmatter: title, image, order
    ├── photo-002.md
    └── ...
    public/photos/
    ├── photo-001.jpg       # оригиналы фотографий
    ├── photo-002.jpg
    └── ...
    
  • Сетка превью с оптимизацией через Astro Image (WebP/AVIF, responsive sizes)
  • Просмотр полноразмерных фотографий: lightbox-библиотека (PhotoSwipe или GLightbox)
  • Один альбом, без категорий

Этап 5. RSS, Sitemap, SEO

5.1. Настроить RSS-ленту

  • Создать src/pages/rss.xml.ts с использованием @astrojs/rss
  • Включить все статьи (title, description, date, link)
  • URL: /rss.xml
  • Для обратной совместимости: добавить редирект /atom.xml/rss.xml (в nginx или через Astro)

5.2. Настроить Sitemap

  • Интеграция @astrojs/sitemap уже генерирует sitemap автоматически
  • Убедиться, что 404 и служебные страницы исключены

5.3. Настроить robots.txt

  • Создать public/robots.txt со ссылкой на sitemap

5.4. Open Graph и мета-теги

  • Уже реализовано в BaseLayout (задача 2.1)
  • Проверить корректность на каждом типе страниц

Этап 6. Сборка и деплой

6.1. Обновить Docker-сборку

  • Новый docker/node/Dockerfile на Node 22 LTS
  • PHP Dockerfile удалить
  • Обновить Dockerfile.nginx.prod:
    FROM nginx:stable
    COPY dist /usr/share/nginx/html
    
  • Добавить конфиг nginx для SPA fallback (404.html) и кэширования статики

6.2. Обновить Taskfile

Финальная версия задач:

tasks:
  dev:        # astro dev (локальная разработка)
  build-prod: # astro build
  deploy:     # build-prod → docker build → ansible

6.3. Проверить деплой

  • Собрать Docker-образ
  • Проверить, что все страницы отдаются корректно
  • Проверить 404
  • Проверить RSS и sitemap

Этап 7. Очистка

7.1. Удалить файлы Sculpin

  • app/ — SculpinKernel, конфиги
  • bundle/ — кастомные бандлы (HtmlPrettier, SiteMap, TwigExtension)
  • source/ — старые шаблоны, ассеты, контент (после проверки, что всё перенесено)
  • composer.json, composer.lock
  • docker/php/Dockerfile
  • webpack.config.js
  • .php-cs-fixer.php

7.2. Обновить package.json

  • Удалить старые зависимости (webpack, babel, vue 2, node-sass, и т.д.)
  • Удалить старые npm-скрипты
  • Оставить только Astro и его зависимости

7.3. Обновить .gitignore

  • Убрать output_dev/, output_prod/, vendor/
  • Добавить dist/, .astro/, node_modules/

7.4. Обновить README.md

  • Описание нового стека
  • Инструкции по локальной разработке и деплою

URL-совместимость

URL статей: /articles/2019-05-01-predictor/. Slug = имя файла без расширения, Astro использует его как есть.

Редиректы в nginx для старых URL (301):

/articles/2019/05/01/predictor/  →  /articles/2019-05-01-predictor/
/articles/2019/06/01/php-serialization/  →  /articles/2019-06-01-php-serialization/
/articles/2019/06/28/storytelling/  →  /articles/2019-06-28-storytelling/
/articles/2019/08/08/yandex-disk-image-hosting/  →  /articles/2019-08-08-yandex-disk-image-hosting/
/articles/2019/09/26/highload-videos/  →  /articles/2019-09-26-highload-videos/
/articles/2020/06/27/interesting-programming-blogs/  →  /articles/2020-06-27-interesting-programming-blogs/
/articles/2020/06/27/type-discriminant/  →  /articles/2020-06-27-type-discriminant/
/articles/2020/11/08/nullable-fields/  →  /articles/2020-11-08-nullable-fields/

Редирект /atom.xml/rss.xml для совместимости RSS-подписок.


Порядок выполнения

Этап 1 (инициализация) → Этап 2 (лейауты) → Этап 3 (контент)
    → Этап 4 (страницы) → Этап 5 (RSS/sitemap) → Этап 6 (деплой) → Этап 7 (очистка)

Этапы 2 и 3 можно частично выполнять параллельно. Этап 7 выполняется только после полной проверки нового сайта.