Я размышлял над созданием туристического сайта romantic-weekend.com, посвящённого направлениям для романтических выходных в Европе и США. Основная идея намеренно проста: люди ищут в Google запросы вроде «романтические выходные в Париже», «романтические выходные в Риме», «романтический отдых в долине Напа» или «романтические выходные во Франции» и попадают прямо на целевую страницу конкретного направления. На сайте будут как более общие посадочные страницы для регионов и стран (например, Европа, Франция, Италия и США), так и отдельные страницы для таких локаций, как Париж, Венеция, Нью-Йорк и Сан-Франциско. Поскольку я ожидаю, что большинство посетителей будут приходить из поиска Google, а не переходить с главной страницы, каждая страница направления должна отлично работать сама по себе, оставаясь при этом частью чёткой общей структуры.
Чем больше я думал о технической стороне проекта, тем меньше мне хотелось использовать традиционную CMS. Romantic-weekend.com не задумывается как сложное приложение. Здесь не нужны учётные записи пользователей, комментарии, админки, воркфлоу, права доступа или сложная модель базы данных. По сути, это редакционный сайт, состоящий из структурированных страниц. Это навело меня на мысль: возможно, самая простая архитектура окажется и самой лучшей — PHP в качестве движка сайта, Markdown-файлы для контента, файловая система для иерархии и обычные PHP-шаблоны для вывода.
Я представляю структуру директории с контентом примерно так:
/content
/europe
index.md
/france
index.md
paris.md
nice.md
annecy.md
/italy
index.md
rome.md
venice.md
/usa
index.md
/california
index.md
san-francisco.md
napa-valley.md
/new-york
index.md
new-york-city.md
Плюс такой структуры в том, что сама файловая система описывает сайт. Файл /content/europe/france/paris.md естественным образом превращается в страницу /europe/france/paris/ , а /content/europe/france/index.md — в /europe/france/ . Мне не нужна таблица в базе данных, где указано, что Париж относится к Франции, а Франция — к Европе, потому что расположение файла уже говорит об этом. Мне также не нужно дублировать URL, родительскую страницу, страну и регион в каждом файле с контентом. PHP может автоматически извлечь большую часть этих данных из пути.
Тогда страница Парижа может выглядеть очень просто:
---
title: "Romantic weekend in Paris"
description: "Plan a romantic weekend in Paris."
image: paris.jpg
---
# Romantic weekend in Paris
Paris is one of the classic destinations for a romantic weekend.
## Where to stay
...
## Romantic things to do
...
## Where to eat
...
Markdown-файл описывает только контент. PHP берёт на себя всё остальное: URL, хлебные крошки, метаданные, шаблоны, похожие направления, канонические URL, записи в sitemap и навигацию. Такое разделение для меня важно, потому что я хочу, чтобы файлы контента оставались легко читаемыми, даже если сайт сильно разрастётся. Если на romantic-weekend.com со временем появятся сотни направлений, добавление нового всё равно должно оставаться предельно простым: создал Markdown-файл в нужной папке — и готово.
Например, добавление Лиона в идеале должно сводиться к созданию /content/europe/france/lyon.md и ничего более. Страница Франции должна автоматически его обнаружить. В sitemap он должен появиться сам. Хлебные крошки должны автоматически принять вид «Европа › Франция › Лион». Блок «Другие романтические выходные во Франции» должен автоматически давать на него ссылку. Система должна понимать иерархию без необходимости поддерживать одну и ту же информацию в пяти разных местах.
Именно поэтому мне нравится идея сделать страницы стран и регионов полноценными контентными страницами, а не просто техническими архивами рубрик. /europe/ может ранжироваться по запросам о романтических выходных в Европе. /europe/france/ — привлекать трафик по запросам о романтических выходных во Франции. /usa/california/ — закрывать запросы по романтическому отдыху в Калифорнии. Эти страницы могут содержать собственный редакторский текст, рекомендации и внутренние ссылки, а PHP автоматически подставит соответствующие дочерние направления. Это даёт сайту понятную SEO-структуру, не заставляя пользователей кликать по всем уровням вложенности, чтобы добраться до нужной страницы.
Первым порывом для обработки Markdown было взять зрелый пакет вроде league/commonmark . Это бы сработало, и для многих проектов это разумный выбор. Но romantic-weekend.com заставил меня усомниться в том, нужна ли мне вообще полная реализация Markdown. Я сам полностью контролирую каждый файл контента. Здесь нет пользователей, которые вставляют произвольный Markdown в форму. Мне не нужно поддерживать все краевые случаи спецификации CommonMark. На практике для редакционного контента, скорее всего, понадобятся только заголовки, абзацы, полужирный текст и курсив, ссылки, списки, цитаты и, возможно, изображения.
А значит, я могу написать намеренно компактный Markdown-парсер вместо того, чтобы тянуть внешнюю зависимость. Разница принципиальна: я не пытаюсь написать парсер, строго соответствующий стандартам. Я просто определяю небольшой формат разметки для конкретного сайта, который использует привычный синтаксис Markdown. Если сайт поддерживает # , ## , **bold** , [links](...) и простые списки — это и есть вся спецификация. Чему-то более сложному просто не место в формате контента, пока я осознанно не решу добавить это позже.
То же самое касается и блока метаданных в начале каждого файла. Мне не нужен полноценный YAML-парсер только ради того, чтобы прочитать три-четыре простых значения. Легковесного парсера, читающего строки key: value , вполне достаточно. Это делает проект максимально переносимым. В самом простом варианте весь стек runtime-зависимостей может состоять из одного лишь PHP.
Эта идея привлекает меня больше, чем я ожидал. Markdown-файлы становятся базой данных. Git — историей версий. Дерево директорий — таксономией. PHP — шаблонизатором и движком рендеринга. Если мне понадобится перенести сайт на другой сервер, я просто копирую проект, и всё работает. Никаких дампов БД для импорта, никаких обновлений CMS и никаких длинных деревьев зависимостей, которые нужно разворачивать ещё до того, как сайт сможет отрендерить хотя бы одну страницу.
Я, скорее всего, всё же буду строить индекс всех файлов контента, а не сканировать файловую систему рекурсивно при каждом запросе. Небольшая build-команда может проходить по /content , считывать метаданные каждой страницы и генерировать PHP-массив с URL, заголовками, путями к файлам, родительскими связями и, возможно, тегами. Этот индекс затем будет использоваться для роутинга, хлебных крошек, списков по странам, похожих направлений и XML-карты сайта.
Сгенерированный индекс может содержать записи такого вида:
return [
'/europe/france/paris/' => [
'title' => 'Romantic weekend in Paris',
'file' => 'europe/france/paris.md',
'type' => 'destination',
],
'/europe/france/lyon/' => [
'title' => 'Romantic weekend in Lyon',
'file' => 'europe/france/lyon.md',
'type' => 'destination',
],
];
После этого роутинг становится элементарным. PHP получает /europe/france/paris/ , находит соответствующую запись, читает файл, конвертирует Markdown, передаёт результат в шаблон направления и возвращает HTML. Если когда-нибудь встанет вопрос производительности, я смогу кэшировать полностью отрендеренную страницу и отдавать готовый HTML напрямую, пока исходный Markdown-файл не изменится. В итоге сайт будет работать почти как статический, сохраняя при этом всю гибкость PHP.
Одна из интересных возможностей — немного выйти за рамки обычного Markdown и добавить несколько специфичных для сайта конструкций. Например, во внутренних ссылках можно использовать идентификаторы вместо захардкоженных URL. Вместо написания Paris я мог бы написать [[paris]], а PHP сопоставил бы этот идентификатор с актуальным URL. Если я когда-нибудь изменю структуру URL, контент править не придётся. Такой же подход сработает и для динамических секций вроде [[related]], [[destinations]] или [[hotels]], где PHP будет заменять токен сгенерированным компонентом.
Это превратило бы формат контента в крошечный DSL (domain-specific language) для romantic-weekend.com. Markdown оставался бы читаемым в виде обычного текста, но при этом мог бы выражать специфичные для этого сайта связи. Посадочная страница Франции могла бы содержать вступительный текст, за которым идёт [[destinations]], а PHP автоматически подставлял бы все французские направления. Страница Парижа могла бы включать [[related]], и движок выбирал бы другие направления на основе географии, тегов или заданных вручную связей.
Мне также нравится идея хранить в файлах контента структурированные атрибуты, которые полезны для рекомендаций, а не только для отображения. Направлению можно присвоить теги city, food, culture, luxury, beach или countryside, а также указать лучшие месяцы для поездки, типичную продолжительность или общий уровень цен. Эти поля позволили бы создавать полезные внутренние ссылки вроде «Другие романтические уикенды в городах», «Романтические направления для осени» или «Похожие на Париж места» без ручного наполнения этих блоков на каждой странице.
Главное, чего я хочу избежать, — это превращения этой небольшой системы в собственный фреймворк. Это лишило бы затею всякого смысла. Если Markdown-парсер начнёт пытаться поддерживать каждый закоулок спецификации CommonMark, мне лучше взять готовую библиотеку для Markdown. Если формат метаданных начнёт разрастаться в сложный язык схем, вероятно, стоит использовать нормальный парсер. Если со временем сайту понадобятся несколько редакторов, воркфлоу, система прав и большая админка, то правильным выбором станет CMS.
Но для сайта в его текущей задумке этих проблем не существует. Romantic-weekend.com — это, по сути, набор редакционных посадочных страниц, организованных в предсказуемую иерархию. Большинство посетителей будут приходить из Google на страницу конкретного направления, поэтому главная задача — не сложность приложения, а создание качественных страниц, их грамотная организация и выстраивание связей между ними так, чтобы это было полезно и поисковикам, и людям.
Для решения этой задачи идея сверхкомпактной системы на PHP кажется мне удивительно привлекательной. Несколько классов, папка с Markdown-файлами, пара шаблонов, сгенерированный индекс, карта сайта XML и HTML-кэш — вот и вся инфраструктура, которая на самом деле нужна сайту.
Есть что-то подкупающее в системе, которую можно полностью понять, просто открыв папку с проектом. Никакой скрытой структуры БД. Никакой экосистемы плагинов. Никаких соглашений фреймворка, о которых нужно помнить. Никаких пакетов, внезапно требующих обновления другого пакета.
Просто файлы на входе, HTML на выходе.
Для romantic-weekend.com это может оказаться именно тем уровнем технологий, который нужен.
Комментарии (0)
Пока нет комментариев — будьте первым.