К содержимому
Type — API сайта
Разделы

Адреса страниц и переадресации

Адрес объекта: slug и slugs

У каждого объекта есть ЧПУ (человекопонятный адрес) на каждом языке. Его хранит CRM:

ПолеЧто значит
slugЧПУ на языке ответа (lang); если для языка ЧПУ нет — румынский
slugsЧПУ на всех языках: { ro, ru, en } — для ссылок на другие языки и hreflang
  • ЧПУ задаётся один раз и не меняется при переименовании объекта, поэтому ссылки и поисковая выдача не ломаются.
  • ЧПУ уникален среди объектов компании на каждом языке. Если название совпадает с уже существующим, CRM добавит суффикс (-2).
  • Если менеджер всё-таки сменит ЧПУ вручную, прежний адрес попадёт в таблицу переадресаций (ниже) — старая ссылка не даст 404.

Пример маршрута на сайте: /{lang}/apartament/{slug}. Схема адресов — на ваше усмотрение; CRM отдаёт только ЧПУ.

tsx
// ссылки на другие языки
<link rel="alternate" hrefLang="ro" href={`https://site.example.com/ro/apartament/${u.slugs.ro}`} />
<link rel="alternate" hrefLang="ru" href={`https://site.example.com/ru/apartament/${u.slugs.ru}`} />

Таблица переадресаций: legacyUrls()

GET /legacy-urls отдаёт список старых адресов. Каждый элемент — SiteLegacyUrl:

ПолеЧто значит
urlстарый адрес: страница товара WooCommerce или прежний ЧПУ объекта
pathпуть старого адреса (/ro/product/<slug>/) — ключ для сравнения с запросом
langязык старого адреса
unit_idобъект
slugтекущий ЧПУ объекта на языке lang; null — объект удалён
gonetrue — объект удалён

Что отвечать на запрос старого адреса:

СлучайОтвет
объект есть в units()301 на /{lang}/apartament/{slug}
объект в архиве (нет в units(), но gone: false)301 на каталог
gone: true410 Gone — страницы больше нет, поисковики уберут её из выдачи

Сравнивайте путь без завершающего / и без учёта регистра; кириллица в старых ссылках может прийти и в процентной кодировке, и декодированной.

Next.js: redirects() при сборке

Подходит, если старые адреса больше не меняются.

ts
// next.config.ts
import { createSiteClient } from '@type/site-client';

export default {
  async redirects() {
    const crm = createSiteClient({ baseUrl: process.env.TYPE_API_URL!, apiKey: process.env.TYPE_API_KEY! });
    const { units } = await crm.units({ lang: 'ro' });
    const live = new Set(units.map((u) => u.id));
    return (await crm.legacyUrls())
      .filter((l) => !l.gone)
      .map((l) => ({
        source: l.path,
        destination: live.has(l.unit_id) && l.slug ? `/${l.lang}/apartament/${l.slug}` : `/${l.lang}/apartamente`,
        permanent: true,
      }));
  },
};

Адреса с gone: true отдайте 410 из middleware или catch-all-маршрута: redirects() умеет только переадресацию.

Next.js: без пересборки

Ведите таблицу в middleware.ts или в catch-all-маршруте (app/product/[...slug]/page.tsx с permanentRedirect()): кэшируйте legacyUrls() через unstable_cache с тегом rental:units и сбрасывайте его вебхуком — новые переадресации появятся без пересборки сайта.

Фото

photos[].url — постоянная ссылка вида /api/modules/rental/site/photos/…. Ключ для неё не нужен, её можно ставить прямо в <img> (через crm.photoUrl(url) — абсолютный адрес). Ссылка отвечает 302 на превью WebP шириной до 1920 px без EXIF, Cache-Control: public, max-age=3000. Если фото удалено или объект в архиве — 404; если превью ещё строится — 503 с Retry-After.