Мультидоменный проект (мультисайт) на NextJS

—

от автора

Привет! Я frontend-разработчик в одной компании, занимающейся электронной коммерцией.

Не буду долго рассказывать о себе, о компании и о том, как возникла потребность написать подобный проект, сразу приступлю к описанию решения.

Представим, что у вас порядка 500-1000 доменов и 5-10 разных дизайнов сайтов, распределенных между этими доменами примерно так:

  1. domain.com, domain-[city].com, domain-[subproduct]-[city].com — «сетка» 1

  2. domain2.com, domain2-[city].com, domain2-[anything]-[city].com — «сетка» 2

  3. 4,5…

Для всех сайтов нужна «одна» админка, БД разные. Для каждой «сетки» один дизайн. На каждом уникальном домене свои уникальные адреса продуктов/товаров/услуг и свои данные по продуктам. Никаких редиректов. Все страницы будем генерировать на сервере (SSG + ISR).

Из-за двух последних условий от SEOшников, вариантов реализации подобного проекта остается не так много.

Я использовал NextJS (Pages router). Так как по адресам domain.com/[firstLevel] может находиться и товар/услуга, и категория товара/услуги, и инфо страница, и любая другая страница, остается только один путь получения данных — полностью получать с бэка всю информацию по странице.

— В папке /pages создал

  • […slug].tsx

  • папку admin

    • [adminFoldersAndRoutes] — в реале не один динамический путь, а много папок и файлов

  • удалил index.tsx

— В next.config.js необходимо прописать следующие настройки, чтобы получать имя хоста в getStaticProps в […slug].tsx:

async rewrites() {     return [       {         source: '/admin/:path*',         destination: '/admin/:path*',       },       {         has: [           {             type: 'host',             value: '(?<host>.*)',           },         ],         source: '/',         destination: '/:host',       },       {         has: [           {             type: 'host',             value: '(?<host>.*)',           },         ],         source: '/:path*',         destination: '/:host/:path*',       },     ];   },

— В getStaticProps в […slug].tsx:

Теперь в context попадает имя хоста в ctx.params.slug и весь путь страницы. С помощью React query делаем

 queryClient.prefetchQuery({         queryFn: () => getPageData({ baseApi, url }),         queryKey: [QUERY_KEY_FETCH_PAGE_DATA, { baseApi, url }]       }),

baseApi определяется на основании полученного хоста.

Также в getStaticProps можно запросить domainData, где будет информация по домену (телефоны, адреса, инфо для шапки/футера, инфо домена: апи и тд…) и доставать эти данные из кэша там, где надо

В getStaticPaths:

export const getStaticPaths = async (): Promise<any> => ({   fallback: 'blocking',   paths: [] })

— Передаю baseApi вниз в компонент динамической страницы (DynamicPage), там повторяю запрос на pageData, достаю данные по странице, которые содержат:

  • тип страницы (switch/case)

  • хлебные крошки

  • содержание (вывод по условиям блоков страниц)

  • СЕО

— Различия по CSS (шрифты, цвета) делаю через <style> в DynamicPage по условиям из domainData.

— В админке необходимо создать управление всеми сущностями, которые есть на сайтах: товары, услуги, категории товаров/услуг, страницы, пользователи, бренды и всё, всё, всё! И самое главное у всех страниц необходимо создать управление СЕО. В итоге — это в основном множество таблиц и полей форм сущностей. Админка вся на getServerSideProps и частично на CSR. На любые методы PUT/PATCH/DELETE/POST необходимо в ответ получать адреса ревалидации с бэка и отправлять на внутреннее api NextJS:

/** обработчик обновления страниц */ export default async function handler (req: NextApiRequest, res:NextApiResponse): Promise<void> {   if (req?.method !== 'POST') {     return res.status(405).json({ message: 'Метод не разрешен' })   }    try {     if (req?.body?.secret !== process.env.REVALIDATE_TOKEN) {       return res.status(401).json({ message: 'Неверный токен' })     }      if (!Array.isArray(req.body.url)) {       return res.status(400).json({ message: 'Не правильный формат урл' })     }      /** промисы */     const revalidatePromises = req?.body?.url?.map(async (url: string) => res.revalidate(url))      /** результаты */     const results = await Promise.allSettled(revalidatePromises)      /** кол-во успешных */     const successfulRevalidations = results.filter(result => result.status === 'fulfilled').length      return res.json({ revalidated: true, successfulRevalidations })   } catch (err) {     return res.status(500).send('Ошибка ревалидации из хэндлера')   } }

Единственное неудобство — необходимость в каждый запрос передавать baseApi.

Честно говоря, до сих пор пишу и поддерживаю этот проект и ощущение, что где-то, что-то делаю не так, но все работает: страницы генерируются, регенирируются по запросу. Важные данные по цене и наличию делаю клиентскими.

Может кто-то делал что-то подобное, интересно мнение по поводу этого подхода.


ссылка на оригинал статьи https://habr.com/ru/articles/808861/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *