Эта страница переведена с английского. Читать страницу на английском
Экраны, помеченные public, может открыть любой, в том числе посетители, которые никогда не входили: посадочная страница, прайс-лист, справочный центр. Zeyn отдаёт их так, чтобы их было дёшево доставлять, а кэшам и поисковым системам было удобно с ними работать.
Без cookie для посетителей
Публичный экран, открытый запросом без сессии, не устанавливает cookie. Страница несёт пустой CSRF-токен, поэтому каждый посетитель с тем же языком и темой получает одни и те же байты.
Среда выполнения в браузере запрашивает токен через GET /_zeyn/csrf непосредственно перед первым вызовом, которому он нужен (форма, действие, выбор языка). Этот вызов устанавливает cookie zeyn_csrf и никогда не кэшируется. Каждый POST проверяется точно так же, как на любой другой странице: без подходящего токена он отклоняется с кодом 403, и ничто не повторяется молча.
Вошедшие пользователи, как и раньше, всегда получают cookie и токен на странице.
HTTP-кэширование
#[screen(route = "/pricing", public, cache = 300)]
pub fn pricing() -> Screen {
Screen::new("Pricing")
}cache = N позволяет браузерам и общим кэшам хранить страницу N секунд (от 1 до 86400). Для запроса без сессии страница тогда получает:
| Заголовок | Значение |
|---|---|
Cache-Control | public, max-age=300 |
ETag | Строгий тег по отрисованному телу |
Vary | x-zeyn-partial, Accept-Language, Cookie |
Запрос с подходящим If-None-Match получает 304 Not Modified без тела. Экраны, загружаемые навигацией внутри приложения, кэшируются так же, со своим тегом.
Запрос с сессией никогда не получает публичных заголовков кэша: страница отправляется с Cache-Control: private, no-store. cache на экране, который не public, не компилируется.
Экран по-прежнему строится и отрисовывается для каждого запроса, дошедшего до сервера; тег экономит передачу, а кэш перед приложением экономит сам запрос. Не помещайте в кэшируемые экраны ничего, что зависит от посетителя: время суток, счётчик, случайный выбор.
Поисковые системы и превью ссылок
#[screen(route = "/pricing", public, cache = 300)]
pub fn pricing() -> Screen {
Screen::new(t!("pricing.title"))
.description(t!("pricing.description"))
.canonical("/pricing")
.og_image("https://cdn.example.com/pricing.png")
.robots(Robots::Index)
.json_ld(serde_json::json!({
"@context": "https://schema.org",
"@type": "Product",
"name": "Team plan",
}))
}| Метод | Теги head |
|---|---|
.description(text) | <meta name="description"> и og:description на языке пользователя |
.canonical(url) | <link rel="canonical"> и og:url |
.og_image(url) | og:image |
.robots(Robots::Index) или Robots::NoIndex | <meta name="robots"> |
.json_ld(value) | <script type="application/ld+json"> |
Каждое значение экранируется. JSON-LD записывается с <, > и & в виде <, > и &, поэтому значение, содержащее </script>, не может закрыть элемент; браузер его никогда не выполняет, а политика безопасности содержимого остаётся такой же строгой, как на любой другой странице. Пути приложения в адресных свойствах JSON-LD (url, @id, image, logo, item и подобных) на любой глубине записываются абсолютными с App::site_url, как ожидают поисковые системы.
URL должны быть адресами https или путями приложения, начинающимися с /. Всё остальное (http:, //host, javascript:, пробелы, кавычки) отбрасывается с предупреждением в журнале. Пути приложения записываются после URL сайта и базового пути:
App::new(t!("app.name")).site_url("https://shop.example.com")Без site_url теги head сохраняют путь приложения, который браузеры разрешают, а некоторые краулеры нет.
Навигация внутри приложения обновляет заголовок и все эти теги без загрузки страницы.
Sitemap
#[action(public)]
pub async fn site_pages(part: SitemapPart, db: Db) -> Result<Vec<SitemapUrl>> {
let rows = db.products_after(part.offset, part.limit).await?;
Ok(rows.into_iter().map(|p| SitemapUrl::new(format!("/products/{}", p.slug)).lastmod(p.changed)).collect())
}
Feature::new("site").sitemap(site_pages)/sitemap.xml запрашивает у действия по 1000 URL за раз (part.offset, part.limit) и передаёт XML потоком, поэтому большой сайт никогда не занимает память целиком. До 50 000 URL это один urlset. Сверх этого это индекс карт сайта из /sitemaps/1.xml, /sitemaps/2.xml и так далее, в каждой по 50 000 URL. Действие должно быть public и возвращать URL в стабильном порядке. Для путей приложения нужен App::site_url, потому что карта сайта содержит абсолютные URL; записи, которые не являются адресами https или путями приложения, отбрасываются. Перечисляйте только публичные страницы: поисковые системы не могут открыть остальные. Карта сайта есть у одной функции (feature) на приложение.
robots.txt и llms.txt
App::new(t!("app.name"))
.robots_txt("User-agent: *\nAllow: /\nSitemap: https://shop.example.com/sitemap.xml\n")
.llms_txt(include_str!("../llms.txt"))Оба файла отдаются всем как обычный текст.