Ворклоги
Сделано.


Проблема: Google требует обязательные поля для Product в структурированных данных
Суть ошибки
Google Search Console выдаёт ошибку:
"Задайте значение для одного из следующих элементов данных: offers, review или aggregateRating"
Согласно документации Google, для разметки Product обязательно наличие хотя бы одного из:
offers— информация о цене и наличииreview— отзыв о товареaggregateRating— средний рейтинг
Без этих данных страница не будет показываться в расширенных результатах поиска (rich snippets).
Типичный кейс
Интернет-магазин с товарами "цена по запросу" или "под заказ". У таких товаров нет фиксированной цены, поэтому разработчик не передаёт offers — и получает ошибку от Google.
Решения
1. Использовать offers без конкретной цены
Schema.org позволяет указать priceSpecification без точной цены:
json{
"@type": "Product",
"name": "Детская площадка Premium",
"offers": {
"@type": "Offer",
"availability": "https://schema.org/InStock",
"priceSpecification": {
"@type": "PriceSpecification",
"priceCurrency": "RUB"
}
}
}
2. Добавить aggregateRating или review
Если есть система отзывов — использовать её:
json{
"@type": "Product",
"name": "Детская площадка Premium",
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.5",
"reviewCount": "12"
}
}
3. Не использовать разметку Product для товаров без цены
Если товар не имеет цены и отзывов — можно использовать WebPage вместо Product. Это не даст rich snippets, но и не будет ошибок.
Рекомендация
Для товаров "цена по запросу" лучше всего передавать offers с availability, но без price. Google примет такую разметку, а пользователи увидят информацию о наличии товара.
Сделано.
1. На главной странице в блоке Ворклоги добавил ссылку "Смотреть все".
2. В шапке ссылка Журнал стала вести на раздел ворклогов.
3. На странице ворклога добавил ссылку на задачу, в рамках которой ворклог выполнен

4. Добавил отдельный сайтмап для ворклогов
https://fi1osof.ru/sitemap/worklogs.xml
5. На странице проекта под задачами добавил ссылку "Все задачи проекта".
Теперь можно увидеть все задачи проекта, а не только текущие активные.
Кстати, страниц будет больше. Гугл съел сайтмап и там только задач он видит 90+ страниц.

Теперь важно проработать страницу задачи, чтобы она была более информативная (по какому проекту, какие логи были, более проработанные ворклоги (чтобы было более контентополезно и т.п.)). Без этого есть риск попасть под писсимизацию. То есть если будет много типовых страниц с малым поличеством контента, такое не нравится ни гуглу, ни яндексу.
А вот в гугле ситуация получше, он быстрее подхватил последние изменения и начал выводить новые страницы.


Пока что в яндексе ситуация такая:

Добавлена страница с серверным заголовком "Удалена навсегда".
import { useEffect } from 'react'
import { useRouter } from 'next/router'
import { Page } from 'src/components/pages/_App/interfaces'
export const Redirect410Page: Page = () => {
const router = useRouter()
useEffect(() => {
const timer = setTimeout(() => {
router.push('/')
}, 3000)
return () => clearTimeout(timer)
}, [router])
return (
<div>
<h1>Удалена навсегда</h1>
<p>
Страница была удалена.
<br />
Вы будете перенаправлены на главную страницу через 3 секунды...
</p>
</div>
)
}
Redirect410Page.getInitialProps = async ({ res }) => {
if (res) {
res.statusCode = 410
}
return {}
}
Добавил более провокационное.
Сейчас задача - получить обратную связь.
Хостинг был выбран вьетнамский, и это тот еще квест. Надо было найти хоть какой-то хостинг с поддержкой хотя бы английского языка. БЫла найден этот: https://www.pavietnam.vn/en/
Он считается крупнейшим вьетнамским хостингом для частных клиентов, но и тут я как будто в начало 2000-ых попал. Несмотря на заявленную поддержку чего угодно, выбрав при установке убунту 24, по факту, получил я только 18-ую, при чем спустя часа полтора, потому что скорее всего все вручную устанавливали. То есть облаками там и не пахнет. Конфигурацую просто так не поменять, поминутной тарификации конечно же нет (только минимум помесячно. Даже не получится просто так в моменте удалить сервер и не тратить бюджеты). В общем, современный хостинг там пока еще не родился.
Адаптивная верстка совсем плохая. Вот такие бывают варианты на некоторых разрешениях:

3. УРЛы на эстонском языке. Тоже не особо критично, но все-таки правильней было бы на англ, тем более что сайт мультиязычный.
А то как-то странно УРЛ выглядит /ru/elektritööd.html
Кстати, next-js такое не кушает. Думаю, любой react-router тоже, потому что там обычно используют регулярки. Тут вот какой адрес получется:
GET /elektrit%C3%B6%C3%B6d.html 404
4. Несуществующий lang="zxx"
<html class="no-js" lang="zxx">
Попутно замечания по исходному сайту: 1. Нет меню в мобильной версии. При этом основное меню не видно.
2. Тексты слишком СЕОшные. Сайт всего несколько страниц и рассчитывать на органику нет особого смысла, а вот для тех пользователей, что приходят по прямой ссылке, он почти нечитаемый.
Доперенес все и опубликовал.

Теперь можно новые фичи допиливать.
Нет, совсем не заслуживает внимания. Пережиток прошлого. Много всего, и одновременно почти ничего. Это не готовый продукт, а заготовка, которую надо допиливать под себя, при чем весьма монструозная и неудобная. И даже в докере запустить проблематично. Одна огромная куча легаси.
Пока что просто можно сколько угодно заказов оформлять на один емейл.
Сейчас большая путаница с путями картинок.
В тв-полях картинки когда выбираешь, записывается относительный путь от корня самого медеасурса, а не от корня сайта. Вот к примеру, полный путь: /userfiles/Dopolnitelnoe_oborydovanie/balka-s-kachelyami-i-kolczami-samson.png а записывает Dopolnitelnoe_oborydovanie/balka-s-kachelyami-i-kolczami-samson.png
Логика MODX-а: "А чо такова? Медиасурс по-умолчанию, бери из него".
А то, что медиасурс по-умолчанию можно сменить (и он менялся), это их не волнует. В итоге картинка пока на фронте держится на кеше phpthumb ,а по прямой ссылке не доступна.
В итоге в ресайзере пришлось накостылять
// Поиск файла с альтернативными префиксами для старых картинок
if (!fs.existsSync(absPath)) {
const altPrefixes = ['images_old/', 'userfiles/', 'images_old/userfiles/']
for (const prefix of altPrefixes) {
const altPath = resolve(`/uploads/`, prefix + src)
const altAbsPath = process.cwd() + altPath
if (fs.existsSync(altAbsPath)) {
absPath = altAbsPath
break
}
}
}
Но надо будет еще выпиливать префикс images_old из УРЛов, а для этого вероятно придется и редиректы прописать.
Фронт в общих чертах сделан.



Обновил пакеты, полетела ошибка
server/schema/types/Analyra/resolvers/analyzeWebPageAccesibility/index.ts:199:40 - error TS2739: Type 'Page' is missing the following properties from type 'Page': localStorage, sessionStorage
199 const axe = await new AxeBuilder({ page }).analyze()
~~~~
node_modules/@axe-core/playwright/dist/index.d.ts:5:5
5 page: Page;
~~~~
The expected type comes from property 'page' which is declared here on type 'AxePlaywrightParams'
Found 1 error in server/schema/types/Analyra/resolvers/analyzeWebPageAccesibility/index.ts:199
Проблему не сразу получилось локализовать. Оказывается, обновился axe-core, который входит в состав @axe-core/playwright, хотя сам @axe-core/playwright остался той же версии, и этот axe-core ожидал на вход объект Page с новыми полями, а объект этот создается другой зависимостью - playwright, которая в свою очередь устанавливается вместе с @playwright/test.
npm list playwright
site-boilerplate@1.12.0 /disks/wd-1000/www/analyra.ru/agent
├─┬ @playwright/test@1.61.0
│ └── playwright@1.61.0
└─┬ n8n@2.1.5
└─┬ @n8n/n8n-nodes-langchain@2.1.4
└─┬ @langchain/community@1.0.5
└── playwright@1.61.0 deduped
Вот @playwright/test и надо было обновить и это решило проблему.
Сделано. Коммит.
if (
(statusCode === 404 || ctx.pathname === '/404') &&
ctx.req?.url &&
ctx.res
) {
const res = ctx.res
const url = `https://freecode.academy${ctx.req.url}`
const response = await fetch(url).catch(console.error)
if (response?.ok) {
res.writeHead(301, {
Location: url,
})
res.end()
}
}
cline на первый взгляд очень интересный, особенно его вариант canban. Он позволяет работать с гит-проектами в минималистичном агентом режиме в классической канбан-доске. Идея в том, что каждая задача - это своя карточка. Карточка создается в беклоге, после чего отправляется в работу. Агент выполняет поставленную задачу и карточка переводится в колонку ревью. Там вы можете задачу уже перевести в Завершена или прям там продолжить общение с агентом в рамках задачи. Как по мне, очень удобно.
