# Миграция от Vite към Next.js: сайтът на Murray Restaurant

Category: Уеб и бизнес

> Технически case study на DIMITROV.code за миграция от Vite към Next.js с React SPA, Sanity CMS, двуезична SEO архитектура, structured data и AI visibility.

## Murray не беше просто проект за смяна на framework

Murray Restaurant е [ливански ресторант в София](https://murrayresto.eu/). Уебсайтът е изграден като основно онлайн представяне на бизнеса: трябва да представя ресторанта и кухнята, да показва актуално меню на български и английски, да приема заявки за резервации и да дава място за събития, новини, галерии и друго съдържание, което се добавя с времето.

**Първата версия на уебсайта беше разработена с React и Vite през пролетта на 2026 г.**, според тогавашния обхват и изисквания на проекта. За тази задача React SPA архитектурата беше напълно достатъчна и сайтът покриваше основните нужди: собствен визуален стил, двуезично съдържание, меню, резервационна форма, PDF менюта и адаптивен дизайн.

С времето Murray започна да се развива отвъд първоначалния обхват. Появи се нужда от повече самостоятелно съдържание, реални BG/EN маршрути, CMS и по-добра основа за техническо SEO и бъдещо развитие. При планирането на следващия етап избрахме Next.js като по-подходяща архитектура и започнахме **миграцията на уебсайта от Vite към Next.js**.

При миграцията ние от [DIMITROV.code](https://evtinwebsite.com/) не започнахме от нулата и не променяхме работещи части само заради смяната на технологията. Основната цел беше да запазим разпознаваемия облик и потребителското преживяване, но да изградим по-подходяща основа за развитието на сайта.

Причината не беше, че [Vite](https://vite.dev/) е лош избор или че [Next.js](https://nextjs.org/) е автоматично по-доброто решение за всеки React проект. Новите изисквания включваха самостоятелни индексируеми страници, отделни URL адреси за българското и английското съдържание, CMS, metadata за конкретните страници и по-ясна машинно четима структура на публичната информация.

Така миграцията към Next.js се превърна не просто в смяна на framework, а в цялостно надграждане на архитектурата на уебсайта.

## Откъде започнахме: React SPA с Vite и един HTML документ

Старата версия на уебсайта на Murray беше React 19 SPA приложение, изградено с Vite 6. Това беше първоначалната архитектура на проекта преди **миграцията от Vite към Next.js**. При production build Vite генерира статичните файлове, а самото React приложение се зарежда клиентски в един HTML документ.

Навигацията също се управляваше изцяло в браузъра. Вместо отделни маршрути, `App.tsx` сменяше текущия изглед чрез React state и записваше промените в browser history. За посетителя това изглеждаше и се държеше като нормална навигация между различни части на уебсайта, но отделните изгледи нямаха собствени URL адреси.

Подобен беше и подходът към двата езика. Българското и английското съдържание идваха от общ TypeScript translations object, а избраният език се пазеше в `localStorage`. При смяната му JavaScript актуализираше `document.title`, `lang`, meta description и част от Open Graph данните.

Тази React/Vite архитектура работеше добре за първоначалния обхват на сайта, но поставяше естествени ограничения при разрастването му. Нямаше отделен `/en/` документ или самостоятелни URL адреси за менюто и останалото съдържание. Съответно тези изгледи нямаха собствени canonical адреси, езикови алтернативи и metadata на ниво страница.

Съдържанието беше достъпно след изпълнението на JavaScript и промяната на локалния state. За сайт, който постепенно се развиваше от компактно представяне на ресторанта към **двуезичен уебсайт с повече самостоятелно съдържание**, тази SPA архитектура започна да поставя ограничения пред структурата, техническото SEO и следващите етапи от развитието на проекта.

## Техническо SEO и ограниченията на стария React/Vite сайт

Важно е да уточним, че старата React/Vite версия не беше лишена от **техническа SEO основа**. `index.html` имаше canonical адрес, title, description, Open Graph данни и `Restaurant` structured data с основна информация за ресторанта. Имаше `robots.txt`, а XML sitemap-ът съдържаше началната страница.

Ограничението отново идваше от SPA архитектурата. Всичко това описваше един основен HTML документ. Sitemap-ът съдържаше само началния URL, а structured data представяше ресторанта, но нямаше отделни `WebPage`, `Menu`, `BreadcrumbList`, `Article`, `Event` или `ImageGallery` обекти за различните части от съдържанието.

Интересното е, че още старата версия имаше собствен `llms.txt`. В него бяха описани ресторантът, контактите, работното време, част от менюто и структурата на сайта. Тоест **machine-readable слой съществуваше още преди миграцията към Next.js**, но беше ръчно поддържан и ограничен до един общ текстов ресурс. Нямаше Markdown версии на отделните страници или връзки между HTML съдържанието и неговите текстови representations.

Ограничения имаше и при управлението на съдържанието. Менюто се генерираше от CSV към TypeScript чрез локални Node scripts, а останалите текстове се поддържаха директно в source code. Подходът работеше, но всяка по-съществена промяна минаваше през проекта и изискваше нов deployment. При разрастването на сайта това постепенно създаде и нужда от **CMS за управление на съдържанието**.

Изображенията също бяха част от статичния проект и се визуализираха с обикновени `<img>` елементи. Имаше lazy loading и preload за ключовото hero изображение, но не и централен image pipeline за автоматично генериране на подходящи варианти според устройството и размера на екрана.

Резервационната форма работеше директно от браузъра към външната услуга, а production версията се публикуваше като статичен Vite build на споделен хостинг.

Това беше напълно работеща архитектура за първата версия на Murray. Причината за последвалата **миграция на сайта към Next.js** не беше технически дефект във Vite, а промяната в изискванията: повече самостоятелно съдържание, по-лесно управление през CMS и по-добре свързани HTML, SEO и machine-readable ресурси.

## Защо Next.js беше подходящата архитектура за Murray

С разрастването на съдържанието се промени и ролята на сайта. Начална страница с няколко вътрешни изгледа е едно. **Двуезичен бизнес уебсайт** с меню, събития, новини, галерии и статии вече изисква различна структура.

Това е важен въпрос и при [индивидуална изработка на уебсайт](https://evtinwebsite.com/izrabotka-na-uebsait): архитектурата трябва да следва реалните нужди на бизнеса и начина, по който сайтът ще се развива, а не просто избрания framework.

Въпросът не беше просто да добавим повече страници. Всяка публична страница трябваше да има собствен стабилен URL, локализирани title и description, canonical адрес, езикова алтернатива, social preview и structured data според съдържанието си. Това беше важно както за структурата на двуезичния сайт, така и за **техническото SEO**.

Паралелно с това менюто, работното време и публикациите трябваше да могат да се обновяват, без всяка промяна да минава през редакция на source code-а и нов deployment. Искахме едни и същи данни да захранват видимото съдържание, structured data, sitemap и останалите машинно четими ресурси, вместо информацията да се поддържа ръчно на няколко места.

Точно тук **Next.js архитектурата** имаше смисъл за Murray. App Router ни даде основа за реални маршрути, Server Components, metadata на ниво страница, route handlers и оптимизация на изображенията. В комбинация със **Sanity CMS** можехме да отделим управлението на съдържанието от кода, без да изграждаме собствена система около първоначалното React SPA.

Това не означава, че Vite не може да бъде част от по-сложна архитектура или че Next.js е универсално по-добрият избор. При Murray **миграцията от Vite към Next.js** беше следствие от посоката, в която уебсайтът вече се развиваше, и от новите технически и content изисквания.

Същият подход използваме и при други проекти за [миграция към Next.js](https://evtinwebsite.com/migratsiya-kam-nextjs): първо определяме какво от съществуващия сайт има смисъл да бъде запазено и кои ограничения действително изискват промяна на архитектурата.

## Как протече миграцията от Vite към Next.js

Миграцията от Vite към Next.js не започна с пренаписване на всичко. Запазихме визуалния език на Murray, основното съдържание, менюто, резервационния процес, PDF менютата и медийните материали. Променихме архитектурата под тях и начина, по който старият React сайт организира и предоставя публичното съдържание.

Монолитният `App.tsx` от старото React SPA беше разделен чрез **Next.js App Router** на реални маршрути и компоненти с ясни отговорности. Началната страница остана на `/`, а основните секции получиха собствени адреси като `/menu/`, `/about/`, `/kontakt/` и `/experiences/`. За английската версия изградихме паралелна структура под `/en/`, така че двуезичният сайт вече да разполага с отделни URL адреси за локализираното съдържание.

Променихме и начина, по който страниците се рендерират. Основното съдържание се генерира на сървъра чрез **Next.js Server Components**, а клиентски JavaScript остава там, където действително е необходим: навигация, резервационна форма, интерактивно меню, галерии, видео, PDF viewer и consent функционалност.

С разрастването на сайта добавихме и самостоятелна структура за събития, галерии, новини и статии. Всеки тип съдържание може да има archive и detail страници със собствен URL, metadata и езикова версия. Така отделните части на сайта вече могат да бъдат адресирани и описвани като самостоятелни страници, вместо да съществуват само като изгледи в едно SPA.

Миграцията включваше и по-малко видимите, но важни за **техническото SEO** части на сайта: обработка на несъществуващи страници и грешки, redirects за преместено съдържание и canonical правила за PDF менютата. Така прехвърлянето към новата Next.js архитектура не означаваше просто нов build, а запазване и правилно пренареждане на вече съществуващото съдържание.

## Next.js архитектурата след миграцията

След миграцията Murray вече не е едно React SPA, което държи съдържанието и навигацията основно в клиента. Новият **Next.js уебсайт** разделя различните отговорности, но ги свързва през общ data layer.

**Next.js App Router** определя публичната BG/EN структура, layouts, metadata и server endpoints. Данните за менюто, ресторанта и публикациите се извличат през отделен server data layer, който комуникира със **Sanity CMS** и обработва fallback и error състоянията.

Над него presentation компонентите изграждат семантичния HTML на отделните страници. **Server Components** поемат основното съдържание, а Client Components се използват само за функционалностите, които действително изискват взаимодействие в браузъра.

Същите източници на данни могат да се използват и извън видимата HTML страница. От тях се изграждат части от structured data, XML sitemap, `llms.txt` и Markdown версиите на публичното съдържание. Така менюто, работното време или една публикация не трябва да бъдат описвани и поддържани независимо във всеки отделен формат.

Последният слой е deployment архитектурата. Next.js приложението се пакетира като standalone server в Docker, image-ът се изгражда през Google Cloud Build и се изпълнява в Cloud Run, а Firebase Hosting стои пред него като публичен hosting слой.

Това е една от съществените промени спрямо старата React/Vite архитектура: HTML страниците, metadata, structured data и machine-readable ресурсите вече могат да стъпват върху едни и същи източници на информация, вместо да се поддържат като отделни, ръчно синхронизирани версии.

## Sanity CMS за управление на съдържанието

**Sanity CMS** не се използва само за публикациите в сайта. През него се управляват менюто, категориите, основни настройки на ресторанта, работното време, временни затваряния, събития, новини, статии и галерии. Така съдържанието на Next.js уебсайта може да се обновява независимо от source code-а.

Съдържателният модел е изграден двуезично. Една публикация може да съдържа българско и английско заглавие, отделни slug-ове за двата езика, кратко описание, основно съдържание, дата и изображения. Така BG и EN версиите остават свързани като един запис в CMS, дори когато публичните им URL адреси са различни.

Това е особено важно за менюто и оперативната информация. Промяна в ястие, цена, работно време или временно затваряне не изисква редакция на source code-а и нов deployment. След публикуване в Sanity актуализираните данни могат да бъдат използвани от сайта при следващото обновяване на съдържанието.

Предвидили сме и поведение при проблем със CMS. Data layer-ът разграничава реално празно съдържание от временна грешка при заявката, а за основна информация като контакти и работно време има контролирани fallback стойности. Така временен проблем с външната услуга не трябва автоматично да оставя сайта без най-важната информация за ресторанта.

**Sanity Studio** е интегрирано директно в Next.js проекта под `/studio/`, но административната част е изключена от публичното обхождане и от machine-readable ресурсите на сайта.

## Двуезичен Next.js сайт с отделни BG/EN URL адреси

В старата React/Vite версия избраният език беше стойност в `localStorage`, а българското и английското съдържание се сменяха в рамките на една и съща страница. След миграцията към Next.js двата езика имат собствена URL структура и server-rendered HTML с правилно зададен `lang`.

Основните страници имат собствени canonical адреси и **hreflang езикови алтернативи** за `bg`, `en` и `x-default`. Така българската и английската версия не са просто два различни текста в един интерфейс, а свързани самостоятелни документи със собствени URL адреси и metadata.

При динамичното съдържание връзката между езиците идва директно от двуезичния content модел в Sanity CMS. Една публикация може да има различен slug на български и английски, а сайтът използва конкретния URL адрес на съответния превод. Ако английска версия или неин slug липсва, не се генерира несъществуваща езикова алтернатива.

Същият модел използва и езиковият switcher. При публикация той не просто добавя или премахва `/en/` от текущия адрес, а отвежда към реално свързаната версия на съдържанието.

Така **многоезичността и международното SEO** вече са част от routing-а, metadata и content архитектурата на Murray, а не само настройка на потребителския интерфейс.

## Техническо SEO в Next.js като част от архитектурата

При новия **Next.js уебсайт** техническото SEO не е отделен слой, добавен след изграждането на страниците. То следва директно routing-а, URL структурата и content модела на сайта.

Началната страница, менюто, историята, контактите и останалите основни секции имат собствени title и meta description, canonical адреси, `hreflang` езикови алтернативи и social metadata. При динамичните страници тези данни се генерират от съдържанието в Sanity CMS, включително заглавие, описание и основно изображение.

**XML sitemap-ът** също се генерира от реалната структура на сайта. В него влизат основните BG/EN маршрути и публикуваното динамично съдържание. Когато разполагаме с реална дата на последна промяна от CMS, тя се използва за `lastModified`. Ако такава информация не може да бъде потвърдена, не поставяме изкуствено текущата дата при всеки build.

Отделно разграничаваме production сайта от preview и другите среди. Публичният Murray може да бъде обхождан и индексиран от търсачките, докато непроизводствените версии получават ограничения чрез `robots.txt` и `X-Robots-Tag`. Административните и API маршрути също не са част от публичното съдържание за обхождане.

PDF менютата остават достъпни за посетителите, но viewer страниците имат `noindex, follow` и canonical към съответното HTML меню. Така удобният PDF формат не се конкурира с основната индексируема HTML версия на съдържанието.

Резултатът е **Next.js SEO архитектура**, която следва реалните URL адреси, страници и съдържание на сайта, вместо да разчита на един общ набор от metadata за целия React сайт.

## Structured data и Schema.org в Next.js сайта

В старата React/Vite версия structured data описваха основно ресторанта чрез един `Restaurant` JSON-LD обект. След миграцията към Next.js **Schema.org структурата** се изгражда според конкретната страница и нейното съдържание.

На ниво сайт използваме `WebSite` и `Restaurant`, а отделните страници могат да добавят `WebPage` и `BreadcrumbList`. Когато съдържанието го изисква, се използват и по-специфични Schema.org типове като `Menu`, `Event`, `Article` и `ImageGallery`.

По-важното е откъде идват тези данни. `Restaurant` schema използва същата информация за адрес, телефон, координати, работно време и други детайли, която се показва на посетителите. `Menu` structured data се изграждат от данните, които захранват HTML менюто, а JSON-LD за публикациите използва същото съдържание от Sanity CMS като видимите заглавия, описания, изображения и дати.

Така structured data не се поддържат като отделно копие на информацията, което трябва ръчно да следва промените по сайта. Когато съдържанието се актуализира в основния му източник, същата промяна може да бъде отразена и в неговото машинно четимо описание чрез Schema.org.

Това не гарантира rich results и не заменя валидирането в инструментите на търсачките. Практическата полза е друга: намалява риска видимото HTML съдържание и structured data да започнат да описват различни неща.

## AI visibility и AI-ready архитектурата на сайта

Под „AI visibility“ \(AI видимост\) тук разбираме техническата достъпност на публичното съдържание за discovery, crawling и parsing от автоматизирани системи. Това включва достъпен HTML, ясна структура на съдържанието и допълнителни machine-readable ресурси. Не означава и не гарантира, че Murray ще присъства или ще бъде цитиран в отговорите на ChatGPT, Gemini, Perplexity или друга AI система.

Практическите проверки за това дали ChatGPT и други AI системи могат технически да достигнат и обработят съдържанието на един сайт сме разгледали подробно в статията [„Може ли ChatGPT да прочете сайта ти?“](https://evtinwebsite.com/blog/mozhe-li-chatgpt-da-prochete-saita-ti-kakvo-vizhdat-ai-sistemite). Там разглеждаме HTML, robots правила, sitemap, structured data и текстови representations. При Murray част от същите принципи са приложени директно в Next.js архитектурата.

### llms.txt като machine-readable индекс

Murray има динамичен `/llms.txt`, който представя ресторанта и служи като индекс към допълнителни машинно четими ресурси. В него има връзки към BG/EN Markdown версии на основните страници и content секциите, както и към актуални публикации от Sanity CMS.

Информацията в `llms.txt` не се поддържа като напълно отделно копие на сайта. Данни като адреса на ресторанта и наличните публикации идват от същия server data layer, който използва и публичният Next.js сайт.

### Markdown версии на HTML страниците за machine-readable достъп

Поддържаните публични страници на Murray имат паралелно **Markdown представяне**. Началната страница например е достъпна като `/index.md`, а останалите следват структура от типа `/path/index.md`. Така основното публично съдържание съществува както като HTML страница за посетителите, така и в структуриран текстов формат.

Markdown документите се генерират от реалното съдържание на сайта. Менюто се преобразува в четими секции с ястия, описания, грамажи и цени, а публикациите от Sanity CMS се представят като структуриран Markdown със заглавия, списъци, връзки, изображения и останалото налично съдържание.

Markdown response-ът използва `text/markdown` и съдържа информация за езика, canonical адреса и връзката с оригиналната HTML страница. Така Markdown версията остава свързана с основния публичен документ, вместо да съществува като независимо копие на съдържанието.

### Връзката между HTML и Markdown

Самото наличие на Markdown файл не е достатъчно, ако останалата архитектура не показва какво представлява той. Затова при поддържаните публични маршрути Murray използва HTTP `Link` headers, които свързват оригиналната HTML страница с нейното Markdown представяне и с `/llms.txt`.

Например при проверка на страницата с менюто:

```bash
curl -sI https://murrayresto.eu/menu/ \
  | grep -iE '^(content-type|link):'
```

Сървърът връща:

```text
content-type: text/html; charset=utf-8
link: </menu/index.md>; rel="alternate"; type="text/markdown",
      </llms.txt>; rel="describedby"; type="text/markdown"
```

В реалния `Link` header има и други ресурси, например preload връзки към шрифтове и изображения. Тук са показани само частите, свързани с Markdown представянето и `llms.txt`.

След това може да се провери и самата Markdown версия:

```bash
curl -sI https://murrayresto.eu/menu/index.md \
  | grep -iE '^(content-type|link):'
```

Отговорът е:

```text
content-type: text/markdown; charset=utf-8
link: <https://murrayresto.eu/menu/>; rel="canonical",
      </llms.txt>; rel="describedby"; type="text/markdown"
```

Така връзката работи и в двете посоки. HTML страницата посочва Markdown версията като алтернативно представяне, а Markdown ресурсът сочи обратно към canonical HTML адреса. И двата ресурса са свързани и с `/llms.txt`.

Тази връзка е реализирана чрез HTTP headers, а не чрез `<link>` елементи в HTML `<head>`.

Важно е и друго ограничение: наличието на `Link` header не е само по себе си доказателство, че Markdown версията на произволен URL съществува. Затова при проверка трябва да се тества и самият `.md` адрес. При поддържания маршрут `/menu/` той връща `Content-Type: text/markdown`, докато неподдържани или несъществуващи Markdown маршрути могат да върнат `404`.

### Какво не е реализирано в AI-ready слоя

В текущата архитектура няма `llms-full.txt`. Няма и отделни правила в `robots.txt` за GPTBot, ClaudeBot, Google-Extended или други AI crawler-и, които в момента попадат под общите правила за обхождане.

Markdown адресите не са част от XML sitemap. Те се откриват чрез `/llms.txt` и HTTP `Link` relations.

Това са границите на текущата **AI-ready имплементация**. `llms.txt`, Markdown версиите и HTTP `Link` relations не могат да накарат ChatGPT, Gemini, Perplexity или друга външна AI система да обходи, запази, използва или цитира съдържанието. Те предоставят по-ясни machine-readable ресурси и връзки между тях на системите, които решат да ги използват.

## Семантичен HTML и crawlability в Next.js

Промяната не е само в URL структурата и metadata. Преработихме и начина, по който самото съдържание се представя в HTML документа.

Публичните страници използват семантични HTML елементи като `<main>`, `<header>`, `<nav>`, `<section>` и `<article>` според ролята на съдържанието. Картите в архивните страници са реални HTML връзки към самостоятелни URL адреси, а съдържанието на публикациите е част от документа, вместо да се появява само като вътрешен изглед след промяна на React state.

**Next.js Server Components** и server-side извличането на данни позволяват основният текст, връзките и навигацията да присъстват в първоначално генерирания HTML. JavaScript остава необходим за интерактивните части на сайта, но достъпът до основното публично съдържание не зависи от това браузърът първо да изпълни цялото React приложение.

Това създава по-ясна HTML структура за посетителите, търсачките, crawler-и и други системи, които извличат информация от публичните страници. За техническото SEO ползата е, че основното съдържание, вътрешните връзки и документната структура са част от самия HTML, а не зависят изцяло от клиентската навигация.

## Оптимизация на изображения, видео и performance в Next.js

Миграцията към Next.js промени и начина, по който уебсайтът обработва изображенията. Локалните assets минават през `next/image`, а изображенията от Sanity CMS се доставят чрез интегрирания image pipeline. Компонентите подават реални размери, `sizes` и описателни alt текстове, така че браузърът да може да получи подходящ вариант според устройството и контекста, в който изображението се показва.

Ключовите hero изображения се зареждат с по-висок приоритет, докато второстепенните изображения могат да бъдат отложени. Външните изображения са ограничени до използвания Sanity CDN, вместо Next.js сайтът да приема произволни remote sources.

Видеото остана част от визуалното преживяване на Murray, но не е необходимо всеки видео файл да се изтегля изцяло още при първоначалното зареждане. Според мястото и предназначението му използваме `preload="none"` или `preload="metadata"`, за да ограничим ненужния първоначален transfer.

Паралелно с това основното съдържание се рендерира чрез Server Components, а client-side JavaScript е концентриран в интерактивните компоненти, вместо да носи цялата страница.

Тези решения дават по-добра техническа основа за **performance оптимизация на Next.js сайта**, но самата архитектура не е benchmark. Затова не приписваме на миграцията конкретни подобрения в Lighthouse, Core Web Vitals, loading time или bundle size без сравними измервания от production среда.

Като моментна проверка на текущата production версия използвахме PageSpeed Insights. При теста от 29 септември 2026 г. началната страница отчете 94 Performance на mobile и 100 на desktop. Тези стойности показват състоянието на сайта към конкретния момент, но не ги използваме като доказателство за причинно подобрение спрямо старата Vite версия, тъй като нямаме идентичен benchmark отпреди миграцията.

![PageSpeed Insights резултати за Murray Restaurant след миграцията към Next.js: 94 Performance на mobile и 100 на desktop](https://cdn.sanity.io/images/l2hfyff5/production/01644659c6f9867a8765677c766d4ebaa3072598-1200x900.webp)

## Външни интеграции, резервации и Google Analytics 4

Резервационната форма беше адаптирана към новата Next.js архитектура. Преди изпращането на заявката server endpoint проверява основните полета, датата и часа, работните дни, временните затваряния и честотата на заявките. Така част от логиката за онлайн резервации се валидира на сървъра, вместо да разчита единствено на JavaScript в браузъра.

Самото изпращане на формата продължава да използва Web3Forms. Не сме изграждали собствена booking система, а сме добавили server-side validation пред съществуващата външна услуга.

Останалите интеграции също запазват конкретна роля: Google Maps за локацията на ресторанта, Bolt Food за онлайн поръчки и Sanity CMS за съдържанието и изображенията.

Google Analytics 4 се зарежда само след изрично съгласие от посетителя, като изборът се запазва локално. Така GA4 analytics кодът не е част от задължителното първоначално зареждане за всеки посетител и остава обвързан с consent логиката на сайта.

## Next.js deployment с Docker, Cloud Run и Firebase Hosting

С миграцията към Next.js се промени и начинът, по който Murray се публикува и изпълнява в production.

При старата Vite версия deployment-ът означаваше генериране на статичния `dist` и качването му на споделен хостинг. Новият Next.js уебсайт работи със server runtime в Google Cloud, тъй като част от функционалността вече изисква изпълнение на сървър.

По-подробно сме разгледали разликите между [български хостинг и cloud платформи](https://evtinwebsite.com/blog/bulgarski-hosting-sreshtu-cloud-platformi-2026) в отделна статия.

Приложението използва `standalone` build и се пакетира в Docker container. Google Cloud Build изгражда production image-а и го публикува в Artifact Registry, след което Next.js приложението се изпълнява в Cloud Run в `europe-west3`.

Firebase Hosting стои пред Cloud Run като публичен hosting слой и насочва заявките към Next.js сървъра. Така публичният домейн запазва единна входна точка, докато зад него Next.js обслужва както страниците, така и server функционалността.

Този deployment модел е необходим за route handlers, server-side validation на резервациите, динамичните machine-readable ресурси и частите от сайта, които използват CMS данни по време на изпълнение.

След миграцията Murray вече не се публикува като статичен Vite export, а като работещо **Next.js server приложение** с Docker, Cloud Run и Firebase Hosting.

## Как миграцията промени текущите разходи

По-развитата архитектура не означава задължително по-висок месечен разход за бизнеса. При Murray към момента се получи точно обратното.

Старата Vite версия работеше на споделен хостинг в Bulinfo с месечен абонамент от приблизително 6 €. След миграцията сайтът използва Cloud Run за Next.js приложението и Sanity за управление на съдържанието.

При текущото натоварване CPU и RAM потреблението на Cloud Run се покриват от безплатното ниво на услугата, а Sanity остава в рамките на Free плана. На практика текущият инфраструктурен месечен разход е близък до 0 €.

| Текущ разход | Преди миграцията | След миграцията |
| --- | --- | --- |
| Hosting / server | ~6 €/месец | ≈0 € при текущото потребление |
| CMS | Няма отделен CMS | 0 € със Sanity Free |
| Домейн | Съществуващ разход | Същият разход |
| Общо за hosting + CMS | ~6 €/месец | ≈0 € при текущото потребление |

_Разходи за hosting след миграцията към Next.js_

Домейнът не е нов разход, породен от миграцията, затова остава извън сравнението на инфраструктурата.

Това не означава, че Cloud Run и Sanity са безплатни независимо от натоварването. Cloud Run има безплатни месечни квоти за compute и заявки, докато мрежовият трафик и потреблението над включените лимити могат да генерират разход. При Sanity също трябва да се остане в рамките на квотите на Free плана.

В конкретния случай обаче преминаването към server runtime, CMS и cloud deployment не увеличи постоянните разходи за основната инфраструктура. При текущото потребление те са практически нулеви и са по-ниски спрямо предишния споделен хостинг.

Това е важна част от архитектурното решение: upgrade на техническата основа не означава автоматично по-скъпа инфраструктура за собственика на сайта.

## Vite срещу Next.js: архитектурата преди и след миграцията

| Област | Преди: Vite версия | След: Next.js версия |
| --- | --- | --- |
| Rendering | Client-rendered React SPA с един основен HTML документ | Server-rendered и prerenderable Next.js страници с Client Components за интерактивните части |
| Routing | Вътрешни view states без отделни URL адреси | Реални BG/EN маршрути, archive и detail страници |
| Езици | localStorage и общ translations object | Отделна URL структура, localized slugs, canonical и hreflang |
| Metadata | Един основен \<head\>, частично променян в браузъра | Metadata на ниво маршрут и конкретна публикация |
| Sitemap | Един ръчно описан URL | XML sitemap с основни маршрути, двуезично CMS съдържание и реални дати на промяна |
| Structured data | Основен Restaurant JSON-LD | WebSite, Restaurant, WebPage, BreadcrumbList, Menu и Schema.org типове според съдържанието |
| Съдържание | TypeScript, CSV и текстове в source code | Sanity CMS за меню, настройки и публикации с контролирани fallback стойности |
| Изображения | Статични assets и \<img\> | Image pipeline с next/image и Sanity изображения |
| Machine-readable слой | Статичен llms.txt | Динамичен llms.txt, Markdown representations и HTTP Link relations |
| Резервации | Директно изпращане от браузъра към Web3Forms | Server-side validation преди изпращането към Web3Forms |
| Analytics | Зареждане след interaction | Google Analytics 4 след изрично потребителско съгласие |
| Deployment | Статичен Vite dist на споделен хостинг | Next.js standalone deployment с Docker, Cloud Build, Cloud Run и Firebase Hosting |

## Какво показва Google Search Console след миграцията

Около месец след миграцията от Vite към Next.js вече можем да видим не само архитектурната промяна в кода, но и как новата URL структура на сайта се отразява в **Google Search Console**.

Един от най-ясните примери е английската версия. В стария React SPA езикът беше избор, съхраняван в `localStorage`, без самостоятелна URL структура за английското съдържание. След миграцията `/en/` е отделна част от двуезичния Next.js сайт със собствени страници за меню, информация за ресторанта, галерии и публикации.

За показания 28-дневен период Google Search Console отчита **52 clicks и 9.64K impressions** за URL адреси под `/en/`. В отчета присъстват английската начална страница, `/en/menu/`, `/en/about/`, галерии и отделни статии.

![Google Search Console отчет за английските страници на Murray Restaurant след миграцията](https://cdn.sanity.io/images/l2hfyff5/production/59623bb559a5dc92b31f51ef71e331edec34eeac-1030x1665.webp)

*Английските страници на Murray в Google Search Console за 28-дневен период. Отчетът показва отделни /en/ URL адреси с impressions и clicks от Google Search.*

Тези данни не са достатъчни, за да припишем конкретен ръст в органичния трафик на миграцията към Next.js. Те показват нещо по-пряко свързано с целта на проекта: английското съдържание вече съществува като самостоятелни URL адреси и Google Search ги отчита като отделни страници.

### Generative AI features в Google Search Console

За същия 28-дневен период Google Search Console отчита 1**.46K impressions в Generative AI features**. В отчета присъстват 49 URL адреса, като сред страниците с най-много импресии са отделни английски статии за ливанската кухня.

![Google Search Console Generative AI features отчет за Murray Restaurant](https://cdn.sanity.io/images/l2hfyff5/production/5ecb44264121939928d79eb1b30f23622a5f63e6-1200x1265.webp)

*Generative AI features в Google Search Console за 28-дневен период. Отчетът показва impressions за отделни страници от Murray, включително публикации, началната страница и менюто.*

Данните показват, че съдържание от Murray присъства в измерваните от Search Console **Generative AI features**. Те не показват кой технически механизъм е довел до това и не доказват, че `llms.txt`, Markdown representations, structured data или друг конкретен елемент от machine-readable архитектурата е причината.

### Резултатът от миграцията не е само в Search Console

Промяната не се изчерпва с видимостта на новите URL адреси. След миграцията към Next.js менюто и публикациите вече са част от server-rendered content архитектура, а metadata, XML sitemap и structured data следват реалните маршрути и съдържание. Sanity CMS отделя редакторската работа от source code-а и позволява оперативните промени да се управляват без намеса в приложението.

Изображенията минават през оптимизационен pipeline, резервационните заявки получават server-side validation, а поддържаните публични страници имат machine-readable Markdown версии, свързани с основното HTML съдържание.

Един месец е кратък период за генерални заключения за SEO ефекта от миграцията от Vite към Next.js. Данните обаче вече позволяват да потвърдим по-тясното и по-важно твърдение за този case study: новата **Next.js content и URL архитектура работи в production**, самостоятелните ѝ страници присъстват в Google Search, а част от съдържанието се отчита и в Generative AI features.

## AI visibility в ChatGPT и Perplexity: небрандови тестове

Данните от Google Search Console показват, че страници от Murray получават impressions в Generative AI features. За да проверим как изглежда **AI discovery извън Search Console**, направихме и ръчни тестове с няколко небрандови заявки в Perplexity и ChatGPT. Въпросите не съдържаха името Murray и не насочваха системите към конкретен сайт.

При няколко небрандови заявки Perplexity включи Murray сред конкретните предложения. Ресторантът се появи както в генерирани отговори, така и сред локалните резултати.

По-интересен беше informational тестът:

> „Кои са най-популярните ливански ястия за начинаещи в София?“

В този случай Murray не присъства само като ресторант. `murrayresto` се появява сред източниците към информацията за конкретни ястия, включително мутабал, фалафел и табуле. Това е различен тип AI visibility: съдържание от сайта е цитирано като източник към тематичен отговор, а не само показано като локален бизнес.

![Perplexity използва murrayresto.eu като източник при небрандов въпрос за ливански ястия в София](https://cdn.sanity.io/images/l2hfyff5/production/12b4ba0af5dbac2c727d07aeead503afdaec234a-2050x1706.webp)

*Небрандов informational тест в Perplexity. При въпрос за популярни ливански ястия murrayresto се появява сред източниците към части от генерирания отговор.*

При заявки като „Къде мога да опитам мутабал в София?“ Perplexity включи Murray сред конкретните предложения. При теста за мутабал ресторантът се появи и в локалния резултат с карта, без името Murray да присъства в заявката ни.

![Murray Restaurant в Perplexity при небрандова заявка „Къде мога да опитам мутабал в София?“](https://cdn.sanity.io/images/l2hfyff5/production/aa11a6aff652886cfc64af3a96ca3dcb7e38a303-3432x2116.webp)

*Небрандов local discovery тест в Perplexity. При въпрос „Къде мога да опитам мутабал в София?“ Murray е включен сред конкретните предложения и в локалния модул с карта.*

Направихме отделна проверка и в ChatGPT. При небрандова заявка за автентична ливанска кухня в София Murray беше включен сред конкретните предложения, заедно с адрес и информация за предлаганата кухня.

![Murray Restaurant сред предложенията на ChatGPT при небрандово търсене за ливански ресторант в София](https://cdn.sanity.io/images/l2hfyff5/production/bb220257d8ddf1fbc118cffc2ce724f76c31a8ba-3432x2116.webp)

*Небрандов discovery тест в ChatGPT. Murray е включен сред предложенията за ливанска кухня в София, без името на ресторанта да присъства в заявката.*

Тези проверки са моментен snapshot, а не измерване на постоянна позиция или гарантирана видимост в AI отговорите. Резултатите могат да се различават според системата, формулировката на въпроса, използваните източници и момента на заявката.

Не можем да заключим и че `llms.txt`, Markdown representations, structured data или друг конкретен елемент от machine-readable слоя е причината Murray да бъде открит или цитиран. Тестовете показват по-тясното и проверимо твърдение: **публичното съдържание на Murray може да бъде откривано при небрандови AI заявки, ресторантът може да бъде включван в генерирани предложения, а съдържание от сайта може да бъде цитирано като източник в тематични отговори.**

## Изводи от миграцията към Next.js

Един от най-важните изводи от Murray е, че добрата **миграция на уебсайт към Next.js или друга нова архитектура** не започва с пренаписване на всичко. Дизайнът, идентичността, основните послания и работещите потребителски flows вече имаха стойност. Задачата беше да изградим по-добра техническа основа под тях, без да губим онова, което работеше.

Проектът показа и колко важен е надеждният source of truth. Когато менюто, работното време и публикациите се използват в HTML, structured data, XML sitemap и Markdown ресурсите, ръчното им дублиране на различни места лесно води до разминавания. Sanity CMS и общият data layer решават не само редакторски проблем, а помагат за последователността на цялата публична информация.

Същият принцип важи и за machine-readable съдържанието. Няма нужда да изграждаме отделен „сайт за AI системи“. По-смислено е публичната информация да бъде достъпна в ясно свързани формати, които стъпват върху едни и същи източници и запазват връзката с canonical HTML страниците. Данните от Google Search Console и ръчните AI discovery тестове показват, че съдържанието на Murray се открива и в такива среди, включително при небрандови заявки. Това обаче не дава основание да приписваме резултата на `llms.txt`, Markdown представянията, structured data или на друг отделен технически механизъм.

Изборът на Next.js има стойност само ако архитектурата действително използва възможностите, заради които framework-ът е избран. Самата смяна на `vite build` с `next build` не би решила нищо. При Murray съществената промяна идва от реалните маршрути, Server Components и server boundaries, локализираната metadata система, общия data layer и начина, по който тези части работят заедно.

И тази работа не приключва с deployment-а. Зависимостите се обновяват, интеграциите трябва да се проверяват, съдържанието продължава да се развива, а техническата архитектура трябва да следва тези промени. Това е и част от работата ни по [Next.js поддръжка и развитие на съществуващи проекти](https://evtinwebsite.com/nextjs-poddrazhka-i-razvitie).

## Често задавани въпроси

### Защо мигрирахте сайта от Vite към Next.js?

Първоначалната React/Vite SPA архитектура беше подходяща за тогавашния обхват на Murray. С развитието на сайта се появи нужда от отделни BG/EN URL адреси, самостоятелни индексируеми страници, CMS, metadata на ниво страница и по-добре свързани machine-readable ресурси. Затова Next.js беше избран за следващия етап на проекта.

### По-добър ли е Next.js от Vite за SEO?

Не универсално. Vite може да бъде част от отлично оптимизиран сайт. При Murray Next.js беше по-подходящ за конкретните изисквания за routing, server-rendered съдържание, metadata, двуезична URL структура и server функционалност. Самата смяна на framework не гарантира по-добро SEO.

### Какво се запази при миграцията от Vite към Next.js?

Запазихме визуалната идентичност, основното съдържание, менюто, резервационния процес, PDF менютата и медийните материали. Основната промяна беше в архитектурата, routing-а, CMS слоя, metadata системата и начина, по който съдържанието се предоставя като HTML и machine-readable ресурси.

### Как се управлява съдържанието след миграцията?

Менюто, работното време, събитията, новините, статиите, галериите и част от настройките се управляват през Sanity CMS. Данните могат да се използват едновременно от публичните страници, structured data, sitemap и други ресурси, без информацията да се поддържа ръчно на няколко места.

### Гарантират ли llms.txt и Markdown версиите видимост в ChatGPT или Perplexity?

Не. Те правят публичното съдържание по-ясно структурирано и предоставят допълнителни machine-readable представяния, но не могат да накарат външна AI система да обходи, използва или цитира сайта. При Murray наблюдаваме реални небрандови discovery и citation примери, но не приписваме тези резултати на конкретен технически механизъм.

> Автор: [Борислав Димитров](https://www.linkedin.com/in/borislav-dimitrov-fizer/)
> Full Stack Developer
> Next.js • SEO • AI Visibility

---

Source: https://evtinwebsite.com/blog/migraciya-ot-vite-kam-nextjs-murray-restaurant
