Оглавление
- Первый старт — с чего начать
- Настройка сайта
- Внешний вид — цвета
- Внешний вид — типографика и шрифты
- Создание записей
- Короткие заметки
- Страницы
- Поиск и черновики
- Nginx — настройка сервера
- Umami — аналитика
- Совместимость браузеров
Первый старт — с чего начать
Прежде чем публиковать записи — настройте три вещи в src/config/theme.ts. Всё остальное можно менять постепенно.
Шаг 1 — Название и описание
export const site = {
name: 'Название вашего блога', // ← поменять
description: 'Краткое описание', // ← поменять
version: 'v0.1.0', // ← можно оставить или убрать ('')
foundedYear: 2026 as number | undefined, // ← ваш год основания или undefined
umamiWebsiteId: undefined as string | undefined, // пока оставить
} as const;
Шаг 2 — Ваши ссылки в подвале
Найдите footer.links и замените на свои:
links: [
{
href: 'https://t.me/ваш_канал',
icon: 'telegram',
label: 'Telegram',
},
{
href: '/rss.xml',
icon: 'rss',
label: 'RSS лента',
},
],
Иконки — SVG-файлы из папки src/icons/. Укажите имя файла без расширения.
Шаг 3 — Навигация в сайдбаре
Найдите navigation и поправьте ссылку на страницу «О блоге»:
export const navigation: NavLink[] = [
{ href: '/p/about', label: 'Моя страница', labelEn: 'About', icon: 'about' },
{ href: '/', label: 'Лента', labelEn: 'Feed', icon: 'feed' },
{ href: '/search', label: 'Поиск', labelEn: 'Search', icon: 'search' },
];
Отредактируйте src/content/ru/pages/about.md — это ваша страница «О себе».
Что НЕ трогать при первом запуске
Эти блоки уже настроены правильно — не удаляйте и не меняйте без необходимости:
| Что | Почему |
|---|---|
fonts.faces[] | Шрифты Roboto и Fira Code подключены и кешируются |
colors | Тёмная/светлая тема уже работает |
categories[] | Все категории (post, video, book, game, photo) настроены |
ui (ru/en) | Строки интерфейса локализованы |
| Helpers в конце файла | Служебные функции — не менять |
Структура файла theme.ts
1. site — название, версия, год, Umami ID
2. typography — размеры шрифтов статей и параграфов
3. spacing — отступы между блоками
4. colors — цвета тёмной и светлой темы
5. textColors — цвета отдельных элементов (лента, статья, навигация)
6. layout — ширина страницы и сайдбара
7. sidebar — иконки, отступы, кнопка темы
8. header — шапка, размер логотипа
9. langToggle — переключатель языков (по умолчанию скрыт)
10. categories — рубрики: post, video, book, game, photo
11. navigation — ссылки в сайдбаре
12. shortNotes — поведение коротких заметок
13. stats — страница статистики
14. feed — лента: количество постов, типографика строки
15. shortNote — оформление карточки заметки
16. feedTags — теги в ленте и статьях
17. fonts — шрифты: семейства, файлы woff2
18. footer — подвал: иконки, ссылки
19. ui — строки интерфейса (ru/en)
20. faqLink — ссылка FAQ в подвале
21. gdprLink — ссылка «Конфиденциальность» в подвале (по умолчанию скрыта)
Настройка сайта
Всё настраивается в одном файле: src/config/theme.ts.
Название, описание, версия
export const site = {
name: 'The Seventy Eight', // название блога в шапке и тегах <title>
description: 'Дневник настоящего времени',
version: 'v0.5.0', // версия — отображается в подвале
foundedYear: 2023 as number | undefined,
umamiWebsiteId: undefined as string | undefined,
} as const;
name — появляется в шапке сайта и в <title> каждой страницы.
version — строка, которую вы сами назначаете. Показывается в подвале справа. Можно убрать, если не нужна — установите пустую строку ''.
Год основания
Поле foundedYear управляет диапазоном лет в подвале:
// Показывает «2023 – 2026» (если текущий год ≠ 2023)
foundedYear: 2023 as number | undefined,
// Показывает только текущий год «2026»
foundedYear: undefined,
Обновлять не нужно — правый год берётся автоматически из new Date().getFullYear().
Umami Website ID
После настройки Umami (см. раздел Umami — аналитика) вставьте ID из дашборда:
// Без аналитики (по умолчанию)
umamiWebsiteId: undefined as string | undefined,
// С аналитикой — вставить реальный ID из Umami Settings → Websites
umamiWebsiteId: 'a13c1aa1-7e44-41a6-9844-1d0bebbc4111' as string | undefined,
ID копируется из Umami dashboard → Settings → Websites → выбрать сайт → Website ID.
Скрипт Umami не подгружается на страницах с
noindex(поиск, категории, теги, пагинация) — только на реальных публикациях. Личные страницы и черновики не трекируются.
Внешний вид — цвета
Цвета тёмной и светлой темы
Секция colors в theme.ts:
export const colors = {
// ── Тёмная тема (по умолчанию) ──
bg: '#0f0f0f', // фон страницы
surface: '#1a1a1a', // фон карточек, кода, инпутов
border: '#2e2e2e', // границы, разделители
text: '#e2e2e2', // основной текст
textMuted: '#888888', // второстепенный текст (даты, мета)
accent: '#c5c5c6', // акцентный цвет (ссылки при наведении, иконки)
// ── Светлая тема ──
bgLight: '#f5f4f8',
surfaceLight: '#eceaf2',
borderLight: '#d8d5e6',
textLight: '#111111',
textMutedLight: '#4a4a4a',
accentLight: '#171717',
} as const;
Пример: сделать акцент ярко-синим
// До
accent: '#c5c5c6',
// После
accent: '#5b8dee',
Пример: светлая тема с тёплым белым фоном
bgLight: '#faf9f7',
surfaceLight: '#f0ede8',
borderLight: '#dedad2',
Цвета по контексту
Секция textColors позволяет задать цвета для конкретных элементов независимо от темы. По умолчанию они ссылаются на переменные темы (var(--text), var(--accent)).
export const textColors = {
// Лента
feedTitle: 'var(--text)', // заголовок поста
feedTitleHover: 'var(--accent)', // заголовок при наведении
feedDate: 'var(--text-muted)', // дата слева
feedDateHover: 'var(--accent)',
// Статья
articleTitle: 'var(--text)',
articleLink: 'var(--accent)', // ссылки в тексте
// Навигация
navLink: 'var(--text-muted)',
navLinkHover: 'var(--text)',
navLinkActive: 'var(--accent)',
// Шапка и подвал
logo: 'var(--accent)',
footer: 'var(--text-muted)',
} as const;
Пример: зафиксировать цвет ссылок независимо от темы
// До — цвет меняется при переключении тем
articleLink: 'var(--accent)',
// После — всегда синий, в любой теме
articleLink: '#4a90e2',
Добавление цвета для категории
У каждой категории есть свой color, который используется в навигации:
{
id: 'post',
label: 'Записи',
icon: 'post',
color: '#7b9cff', // ← цвет иконки этой категории
...
},
Внешний вид — типографика и шрифты
Размеры шрифтов
Секция typography в theme.ts:
export const typography = {
base: '16px', // базовый размер — используется как html font-size
// Заголовки в статьях
h2: '1.3rem',
// Страница статьи
articleTitle: '1.75rem',
articleBody: '1rem',
articleMeta: '0.875rem', // дата, мета-информация
codeInline: '0.9em',
codeBlock: '0.875rem',
// Параграфы
p: '1rem',
pLineHeight: 1.7,
pMarginBottom: '1rem',
lineHeight: {
normal: 1.5, // шапка, сайдбар
relaxed: 1.75, // статьи
},
} as const;
Пример: сделать текст статьи крупнее
// До
articleBody: '1rem',
// После — чуть крупнее, особенно удобно на мобильном
articleBody: '1.05rem',
Подключение собственного шрифта
Шаги:
- Скачайте файл
.woff2(например, на gwfh.mranftl.com) - Положите в
public/fonts/inter-regular.woff2 - Добавьте запись в
fonts.faces:
faces: [
{ family: 'Inter', src: '/fonts/inter-regular.woff2', weight: '400', style: 'normal', preload: true },
{ family: 'Inter', src: '/fonts/inter-bold.woff2', weight: '700', style: 'normal' },
]
- Укажите шрифт в
fonts.families:
families: {
body: "'Inter', system-ui, sans-serif",
heading: "'Inter', system-ui, sans-serif",
code: "'Fira Code', ui-monospace, monospace",
...
}
Разные шрифты для RU и EN
Если хотите использовать разные шрифты для русских и английских страниц:
families: {
body: "'Roboto', system-ui, sans-serif", // для RU
bodyEn: "'Inter', system-ui, sans-serif", // для EN (если undefined — используется body)
heading: "'Roboto', system-ui, sans-serif",
headingEn: undefined, // EN использует heading (Roboto)
}
Текущие шрифты
| Роль | Шрифт | Файл |
|---|---|---|
| Основной текст | Roboto | public/fonts/roboto-v51-*.woff2 |
| Код, даты | Fira Code | public/fonts/fira-code-v27-*.woff2 |
Создание записей
Структура файлов
Все записи хранятся в src/content/ru/ (русские) и src/content/en/ (английские).
src/content/ru/
├── post-slug.md ← запись категории "post" (Записи)
├── video-slug.md ← видео
├── book-slug.md ← книги
├── game-slug.md ← игры
├── photo-slug.md ← галерея
├── short/
│ └── 2026-03-18.md ← короткая заметка
└── pages/
└── about.md ← страница "О блоге"
URL записи совпадает с именем файла: my-post.md → /ru/article/my-post
Все поля frontmatter
---
title: Название записи
date: 2026-03-18
description: Краткое описание для поиска и мета-тегов
draft: false # не обязательно (по умолчанию false)
searchable: true # не обязательно (по умолчанию true)
category: post
tags: [anime, review, 2026]
cover: /images/my-post-cover.jpg
background: /images/my-post-bg.jpg
---
Текст статьи начинается здесь.
Минимальный вариант — только обязательные поля:
---
title: Название записи
date: 2026-03-18
---
Текст статьи.
Описание полей
title — заголовок записи. Обязательное поле.
date — дата публикации в формате YYYY-MM-DD. Влияет на порядок в ленте.
description — краткое описание. Показывается в поиске и в <meta description>. Если не задан — используется начало текста.
draft: true — черновик. Запись не появляется в ленте, поиске и RSS. Доступна по прямой ссылке если знать URL.
# Черновик — скрыт везде
draft: true
# Опубликована (по умолчанию)
draft: false
searchable: false — запись видна в ленте и RSS, но не попадает в поиск (/search). Полезно для записей, которые лучше читать в контексте ленты.
# Исключить из поиска, но оставить в ленте
searchable: false
category — рубрика записи. Доступные значения:
| Значение | Раздел | URL |
|---|---|---|
post | Записи | /ru/post/ |
video | Видео | /ru/video/ |
book | Книги | /ru/book/ |
game | Игры | /ru/game/ |
photo | Галерея | /ru/photo/ |
# Рубрика «Книги»
category: book
tags — список тегов. Синтаксис:
# Правильно — квадратные скобки
tags: [anime, review, 2026]
# Правильно — с пробелами в кавычках
tags: ["мои мысли", anime, 2026]
# Правильно — блочный стиль
tags:
- anime
- review
# НЕПРАВИЛЬНО — незакрытая кавычка (Astro не загрузит файл!)
tags: ["трамвай, "автобус"]
Обложка статьи
cover — изображение, которое показывается в начале статьи и в Open Graph (превью ссылки).
cover: /images/my-post-cover.jpg
Положите файл в public/images/my-post-cover.jpg. Рекомендуемые размеры: 1200×630 px (стандарт OG), формат JPEG или WebP.
Фоновое изображение в ленте
background — тонкое фоновое изображение для строки в ленте. Видно как атмосферный фон справа от заголовка.
background: /images/my-post-bg.jpg
Рекомендуемые размеры: 1600×400 px, широкое горизонтальное изображение. Формат: JPEG 80–85% или WebP.
Поведение фона настраивается в theme.ts → feed:
rowBgSize: 'cover', // 'cover' | 'auto 100%' | 'auto 150%'
rowBgPosition: 'right center', // откуда виден фон
Короткие заметки
Короткие заметки — это небольшие записи, которые показываются в общей ленте в виде карточек.
Создание заметки
Файл: src/content/ru/short/2026-03-18.md
---
date: 2026-03-18
title: Короткий заголовок заметки
---
Текст заметки. Мысли, наблюдения, ссылки.
title рекомендуется — по нему работает поиск, и его видно в результатах. Если не задан — в поиске используются первые 60 символов текста, что хуже читается.
Без заголовка (минимум):
---
date: 2026-03-18
---
Текст заметки.
Исключить из поиска:
---
date: 2026-03-18
searchable: false
---
Настройки коротких заметок
В theme.ts → shortNotes:
export const shortNotes = {
enabled: true, // включить / выключить
showInNav: true, // показывать в меню
showInFeed: true, // включать в общую ленту
label: 'Мысли', // название в меню
labelEn: 'Notes',
icon: 'short', // иконка из src/icons/
path: '/short',
} as const;
URL отдельной заметки: /ru/short/2026-03-18
Индекс всех заметок: /ru/short (не индексируется поисковиками)
Страницы
Страницы — это отдельные материалы: «О блоге», «Контакты», этот FAQ.
Файлы: src/content/ru/pages/about.md, src/content/ru/pages/faq.md
Создание страницы
---
title: О блоге
date: 2024-01-01
description: Краткое описание для мета-тегов
summary: Подзаголовок под h1 (показывается на странице)
draft: false
searchable: true
cover: /images/about-cover.jpg
---
Текст страницы...
URL: /ru/p/about, /ru/p/faq
Добавить ссылку в навигацию
В theme.ts → navigation:
export const navigation: NavLink[] = [
{ href: '/p/about', label: 'Моя страница', labelEn: 'About', icon: 'about' },
{ href: '/', label: 'Лента', labelEn: 'Feed', icon: 'feed' },
{ href: '/search', label: 'Поиск', labelEn: 'Search', icon: 'search' },
];
Чтобы добавить ссылку на свою страницу — добавьте строку в массив. Иконка — имя SVG-файла из src/icons/ (без расширения).
FAQ-ссылка в подвале
В theme.ts → faqLink:
export const faqLink = {
enabled: false, // true — показывать ссылку FAQ в подвале
href: '/p/faq',
icon: 'faq',
label: 'FAQ',
labelEn: 'FAQ',
} as const;
Поиск и черновики
Как работает поиск
Поиск доступен по адресу /ru/search. Он работает на клиенте через библиотеку Fuse.js — без серверных запросов.
Индекс поиска: /ru/search.json (генерируется при сборке).
В индекс попадают:
- Записи (
postRu): не черновики +searchable: true - Страницы (
pagesRu): не черновики +searchable: true - Короткие заметки (
shortRu): толькоsearchable: true
Управление индексацией
| Нужно | Frontmatter |
|---|---|
| Виден везде (стандарт) | ничего не указывать |
| Скрыть из поиска, но оставить в ленте | searchable: false |
| Скрыть везде (черновик) | draft: true |
Как работает robots.txt
Файл public/robots.txt уже настроен правильно:
User-agent: *
Allow: /
Disallow: /ru/search
Disallow: /en/search
Disallow: /ru/page/
Disallow: /en/page/
Disallow: /ru/tag/
Disallow: /en/tag/
Disallow: /ru/post/
...
Disallow: /ru/short/$
Disallow: /en/short/$
Sitemap: https://theseventyeight.org/sitemap-index.xml
Одиночные статьи (/ru/article/…), страницы (/ru/p/…) и заметки (/ru/short/slug) — индексируются.
Служебные страницы (поиск, теги, пагинация, категорийные списки) — не индексируются.
Nginx — настройка сервера
Готовый конфиг находится в templates_nginx.conf в корне репозитория.
Быстрое применение
# Шаг 1. Скопировать на сервер
scp templates_nginx.conf project@ваш-сервер:/tmp/
# Шаг 2. Применить (Раздел 2 файла — готовый конфиг)
sudo cp /tmp/templates_nginx.conf /etc/nginx/conf.d/theseventyeight.conf
# Шаг 3. Проверить синтаксис — ОБЯЗАТЕЛЬНО
sudo nginx -t
# Ожидаемый ответ:
# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful
# Шаг 4. Применить без остановки сайта
sudo systemctl reload nginx
Полный конфиг сервера
server {
server_name theseventyeight.org www.theseventyeight.org;
root /home/project/nana/dist;
index index.html;
charset utf-8;
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 1024;
gzip_types
text/html text/css text/plain
application/javascript application/json
application/xml application/rss+xml image/svg+xml;
# Кеш — _astro/ и fonts/ навсегда (хэш в имени файла)
location /_astro/ {
add_header Cache-Control "public, max-age=31536000, immutable";
expires 1y;
}
location /images/ {
add_header Cache-Control "public, max-age=604800";
expires 7d;
}
location /fonts/ {
add_header Cache-Control "public, max-age=31536000, immutable";
add_header Access-Control-Allow-Origin "*";
expires 1y;
}
location ~* \.html$ {
add_header Cache-Control "no-cache";
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# i18n редиректы: старые URL → /ru/...
rewrite ^/article/(.*)$ /ru/article/$1 permanent;
rewrite ^/short/(.*)$ /ru/short/$1 permanent;
rewrite ^/p/(.*)$ /ru/p/$1 permanent;
rewrite ^/tag/(.*)$ /ru/tag/$1 permanent;
rewrite ^/page/(.*)$ /ru/page/$1 permanent;
rewrite ^/(post|video|book|game|photo)(/.*)?$ /ru/$1$2 permanent;
rewrite ^/search$ /ru/search permanent;
rewrite ^/stats$ /ru/stats permanent;
location / {
try_files $uri $uri.html $uri/index.html =404;
}
location = /rss.xml {
add_header Content-Type "application/rss+xml; charset=utf-8";
add_header Cache-Control "public, max-age=3600";
}
# Umami Analytics (трекинг публично, дашборд только через SSH)
location = /analytics/script.js {
proxy_pass http://127.0.0.1:3000/script.js;
proxy_set_header Host $host;
add_header Cache-Control "public, max-age=86400";
}
location /analytics/api/ {
proxy_pass http://127.0.0.1:3000/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location = /site.webmanifest {
add_header Cache-Control "public, max-age=86400";
add_header Content-Type "application/manifest+json";
}
error_page 404 /404.html;
location = /404.html { internal; }
access_log /var/log/nginx/theseventyeight.access.log;
error_log /var/log/nginx/theseventyeight.error.log warn;
# SSL (управляется Certbot — не менять вручную)
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/theseventyeight.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/theseventyeight.org/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
# HTTP → HTTPS редирект
server {
if ($host = theseventyeight.org) {
return 301 https://$host$request_uri;
}
listen 80;
server_name theseventyeight.org www.theseventyeight.org;
return 404;
}
Umami — аналитика
Umami — это self-hosted аналитика. Работает в Docker на сервере.
Что установлено
| Компонент | Расположение |
|---|---|
docker-compose.yml | umami/docker-compose.yml |
.env (пароли) | umami/.env ← не в git |
.env.example | umami/.env.example |
Первый запуск
# 1. Зайти в папку umami на сервере
cd ~/nana/umami
# 2. Скопировать шаблон окружения
cp .env.example .env
# 3. Сгенерировать пароли (каждый раз — новые!)
openssl rand -hex 16 # для POSTGRES_PASSWORD
openssl rand -hex 16 # для APP_SECRET
# 4. Вставить пароли в .env
nano .env
# 5. Запустить
docker compose up -d
# 6. Проверить что работает
docker compose ps
docker-compose.yml
services:
umami:
image: ghcr.io/umami-software/umami:postgresql-latest
ports:
- "127.0.0.1:3000:3000" # только localhost — наружу не открыт!
environment:
DATABASE_URL: postgresql://umami:${POSTGRES_PASSWORD}@db:5432/umami
APP_SECRET: ${APP_SECRET}
DISABLE_TELEMETRY: 1
depends_on:
db:
condition: service_healthy
restart: unless-stopped
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: umami
POSTGRES_USER: umami
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- umami-db:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U umami"]
interval: 5s
timeout: 5s
retries: 10
restart: unless-stopped
volumes:
umami-db:
.env (шаблон)
POSTGRES_PASSWORD=сюда_вставить_сгенерированный_пароль
APP_SECRET=сюда_вставить_сгенерированный_секрет
Доступ к дашборду
Дашборд не открыт публично — только через SSH-туннель.
Команда (заменить на свои значения):
ssh -L 3000:localhost:3000 ВАШ_ПОЛЬЗОВАТЕЛЬ@IP_СЕРВЕРА -p ВАШ_ПОРТ
Пример:
# project — имя пользователя на сервере (у вас может быть root или другое)
# 1.2.3.4 — IP-адрес сервера (или домен — оба варианта работают)
# 6543 — ваш SSH-порт (стандартный: 22)
ssh -L 3000:localhost:3000 project@1.2.3.4 -p 6543
Если хотите оставить туннель в фоне и вернуться в терминал — добавьте &:
ssh -L 3000:localhost:3000 project@1.2.3.4 -p 6543 &
После подключения открыть в браузере: http://localhost:3000
Логин по умолчанию: admin / umami — сменить сразу после первого входа!
Подключить к блогу
- Зайти в Umami → Settings → Websites → Add website
- Скопировать Website ID
- Вставить в
theme.ts:
umamiWebsiteId: 'ваш-id-здесь' as string | undefined,
- Пересобрать и задеплоить блог
Управление
# Остановить
docker compose down
# Перезапустить
docker compose up -d
# Логи
docker compose logs -f umami
# Обновить до новой версии
docker compose pull && docker compose up -d
Совместимость браузеров
Блог тестировался на современных браузерах. Известные проблемы:
Jelly (LineageOS Browser)
Поддержка отсутствует — особенно на старых версиях LineageOS. Возможны проблемы с:
- CSS-переменными (
var(--...)) color-mix()(смешивание цветов в теме)- JavaScript-островами (Preact)
Это браузер на устаревшем движке WebKit — фиксить нецелесообразно.
Браузерные расширения
Ошибки вида moz-extension://... или chrome-extension://... в консоли — это баги расширений браузера, не сайта. Игнорировать.
Кеш браузера
После деплоя новой версии у пользователей может остаться старый кеш. HTML-страницы намеренно кешируются с Cache-Control: no-cache — браузер проверяет свежесть при каждом визите. JavaScript и CSS в /_astro/ кешируются навсегда (имя файла содержит хеш — при обновлении меняется).
HTTP 304
Ответ сервера 304 Not Modified — это норма. Браузер спрашивает «файл изменился?», сервер отвечает «нет» — браузер использует кеш. Не ошибка.
Свайп «назад» в браузере
Свайп влево (iOS Safari, Chrome Android) работает как стандартная кнопка «назад» браузера — поддерживается нативно, без кода. Кнопка «Назад к ленте» в конце статьи всегда возвращает на главную страницу ленты — независимо от истории навигации.