# План миграции сайта 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`. Должен включать: - ``, мета-теги (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` - Заголовок статьи `

` - Дата публикации и 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`: ```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-компонент `
`. Шаги: - Переименовать файл в `.mdx` - Обновить Vue-компонент `PredictorDemo.vue` до Vue 3 (Composition API или Options API) - `@anwinged/predictor` — чистый TS, совместимость с Vue 3 не затронута - Перенести scoped-стили компонента на Tailwind или оставить scoped CSS - В MDX-файле: импортировать компонент и использовать `` - Удалить ручное подключение 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`: ```dockerfile FROM nginx:stable COPY dist /usr/share/nginx/html ``` - Добавить конфиг nginx для SPA fallback (404.html) и кэширования статики #### 6.2. Обновить Taskfile Финальная версия задач: ```yaml 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 выполняется только после полной проверки нового сайта.