// статьи / статья
#статья #статьи#продукт#модерация

> Статьи как продукт: почему мы отказались от unlisted

От пяти статусов к двум понятным: предпросмотр, обложки, модерация. Логичное продолжение статьи «Статьи на InIProject: как устроен раздел».

SkilinPur 25.10.2026 5 мин чтения серия: как мы делали 👁
Статьи как продукт: почему мы отказались от unlisted

Идея «опубликовано, но скрыто» кажется удобной ровно до момента, когда надо объяснить, кто и где это видит. Мы отказались. Осталось два состояния: опубликовано и нет.

Как появился unlisted

Сначала статусов было больше. Казалось, что статья может быть:

  • черновиком;
  • на проверке;
  • опубликованной;
  • отклонённой;
  • скрытой;
  • «опубликованной, но не в списке».

Последнее — аналог unlisted на YouTube. Статья доступна по прямой ссылке, но её нет в списке и в sitemap. Удобно? На бумаге — да. Можно показать текст человеку, не анонсируя его.

На практике это создало вопросы:

  • Чем «скрытая» отличается от «черновика»?
  • Если статья доступна по ссылке, она опубликована или нет?
  • Почему её нет в списке, но Google её видит?
  • Что делать, если ссылка утекла?
  • Как объяснить это автору, который видит статус «опубликовано», но не находит текст на сайте?

Чем больше я отвечал на эти вопросы, тем яснее становилось: это не гибкость, а неопределённость.

Два публичных состояния

В итоге публично есть только два состояния:

  1. Опубликовано — статья видна в /articles/, в sitemap, по прямой ссылке. Всё.
  2. Не опубликовано — статья не видна никому, кроме автора, админа и того, у кого есть ссылка предпросмотра.

Внутри «не опубликовано» есть рабочие статусы:

  • draft — черновик;
  • pending — на проверке;
  • rejected — отклонена с причиной.

Но это внутренние состояния. Для читателя их нет. Для поисковика их нет. Для сайта есть только «есть статья» или «нет статьи».

Кнопка «Снять с публикации» возвращает статью в draft. Не в «скрытую», не в «unlisted». В черновик. Если нужно снова показать — публикуешь заново.

Почему это лучше

Меньше кода

Каждое состояние — это ветка в шаблоне, проверка в экспорте, фильтр в sitemap, объяснение в интерфейсе. Чем меньше состояний, тем меньше мест, где можно забыть проверку.

Когда убрали unlisted, стало проще:

  • экспорт статей: только published;
  • sitemap: только published;
  • страница статьи: draft/pending/rejected — 404;
  • предпросмотр: отдельный токен, не статус;
  • список в кабинете: три понятных блока.

Меньше вопросов

Автор больше не спрашивает: «Почему статья в статусе “опубликовано”, но её нет в списке?» Такого статуса нет.

Читатель не находит «полустатью». Если ссылка ведёт на черновик — 404. Если на опубликованную — всё работает.

Предпросмотр вместо «временной публикации»

Раньше, чтобы показать черновик, приходилось либо публиковать и прятать, либо делать unlisted. Теперь есть preview_token:

/article/?id=42&preview=8f3c...

Ссылка работает для черновика, не индексируется, токен можно перевыпустить. Это честнее: статья не становится публичной, пока её не опубликовали.

Как это устроено технически

В таблице posts есть status и preview_token.

Публичная страница:

  1. Ищет статью по id.
  2. Если status === 'published' — показывает.
  3. Если статус другой — проверяет preview и сравнивает с токеном.
  4. Если токен не совпал — 404.

Список /articles/ и data/articles.json содержат только published. Sitemap — только published. Блог — отдельно, статьи туда не попадают.

Кнопка «Снять с публикации»:

UPDATE posts SET status = 'draft', published_at = '' WHERE id = ?

Всё. Никаких «скрытых» флагов.

Где были грабли

«Опубликованные» смешивались с «На проверке»

Это не про unlisted, но про ту же путаницу статусов. В кабинете статьи со статусом pending попадали в список опубликованных. Автор видел статью в «Опубликованных» и думал, что она вышла.

Пришлось разделить списки:

  • «Черновики» — draft + rejected;
  • «На проверке» — pending;
  • «Опубликованные» — published.

Урок: статус в интерфейсе должен совпадать со статусом в жизни. Иначе интерфейс врёт.

Ссылка на статью в блоге

Когда статьи запускались, в блоге оставались карточки статей. Получалась «статья в статье»: блог и статьи — разные разделы, а карточки пересекались.

Убрали статьи из блога. В /devblog/ — только короткие заметки. В /articles/ — только статьи. Ссылка на статьи — отдельный пункт меню.

Урок: если разделы разные, у них должны быть разные списки. Иначе пользователь не понимает, где он.

Обложки и предпросмотр

Черновик с обложкой выглядит как готовая статья. Это нормально, но может сбивать. В кабинете статус виден явно, в предпросмотре — тоже.

Обложки генерируются отдельно: 1200×630, html2canvas, можно скачать и загрузить. Это не часть публикации, а инструмент.

Почему не оставить unlisted «на всякий случай»

Потому что «на всякий случай» — это состояние, которое никто не тестирует. Оно есть, но им не пользуются. Потом кто-то пользуется, и оказывается, что оно ломает sitemap или показывает черновик в поиске.

Если понадобится «секретная ссылка» — для этого есть preview token. Если понадобится «статья для своих» — это уже не статья, а другой формат: закрытый раздел, рассылка, документ.

Урок: не добавляй состояние, если не можешь описать его одной фразой и показать, где оно видно.

Что в итоге

Сейчас в проекте пять опубликованных статей. Одна удалена. Черновики и материалы на проверке не видны публично.

Мне нравится, что раздел растёт не «пачкой», а по одной статье. Лучше пять нормальных текстов, чем двадцать «скоро».

Вместо вывода

Unlisted кажется удобным, пока не начинаешь объяснять, что он значит. Для небольшого проекта проще два состояния и предпросмотр по токену.

Продукт — это не только то, что можно сделать. Это ещё и то, что можно объяснить. Если состояние требует абзаца объяснений, возможно, оно лишнее.

// реакции
SkilinPurучастник InIProject