A year on Vercel and one evening of moving: bazariara.ge has become faster and is opening again in Russia
Bazari Arais an online store of Georgian products, gifts and travel equipment in Tbilisi. Next.js 16, PostgreSQL, images in object storage. The project is about a year old, and all this year he lived on Vercel with a base in Neon.
We transferred it to RelaxDev and did not expect such a result:
- ✅100 out of 100on three checks: PageSpeed Insights, speed and site analysis in PR-CY;
- ⚡ from Tbilisi the server responds for0.12–0.14 sinstead of 1.5–3.5 s;
- 🇷🇺 the site opens again from Russia, from Moscow the answer is0.02–0.03 s;
- 🪶 the page is 7 times lighter, the main HTML is three times lighter, the picture in the catalog is 10 times lighter.
Result: 100 out of 100
Why did you leave Vercel?
| Problem | What this meant for the store |
|---|---|
| 🚫Site not available in Russia | Regular providers and mobile operators did not open their stores. Main reason for moving |
| 🗄Base limits | Neon's free plan had limits, and I didn't want to pay for the database separately from hosting |
| 🧊Aggressive cache | Vercel gave away old product cards for a long time: the price or availability was changed, but visitors see the same |
| 🖼Quota for image optimization | Image optimization had to be turned off to avoid burning out the quota. The catalog gave away originals at 100–450 KB |
| 🐢Cold start | The first visitor after a pause waited 1.5–3.5 seconds for the server’s response |
Where to move: Russia, Türkiye or Poland
Almost all buyers are from Georgia, some are from Russia. Before moving, we measured all the options: pings between servers, requests from servers and Globalping - three points each in Tbilisi (Magticom, TradeZone, Cloud 9) and three in Moscow and St. Petersburg (Yandex.Cloud, Timeweb, Selectel).
Candidates: -Vercel— where the site lived; -Russia— RelaxDev, our server in Moscow; -Türkiye- test server: you can move the application there or allow visitors through it; -Polandis the European node of RelaxDev, through which you can also receive visitors.
Pings between servers
| From → to | Ping, average | Scatter |
|---|---|---|
| Moscow → Türkiye | 83 ms | 78–87 ms |
| Türkiye → Moscow | 79 ms | 78–80 ms |
| Moscow → Vercel | 65ms | 46–133 ms |
| Türkiye → Vercel | 10ms | 9–11 ms |
Requests to the site from servers
Three requests in a row. Here we measured a redirect response from / to /ru, so this is the time of the server, not the entire page.
| From → to | Connection | First byte |
|---|---|---|
| Moscow → Vercel | 48–199 ms | 0.24–0.88 s |
| Türkiye → Vercel | 43–69 ms | 0.20–0.40 s |
| Türkiye → RelaxDev in Russia | 0.25–5.16 s | 0.84–5.36 s |
The last line is the main discovery. The “Türkiye → Russia” channel is unstable on new connections: connection sometimes takes five seconds.
How close is each site to customers?
Globalping, connection time (TCP) - pure network, no server running.
| Playground | from Tbilisi | from Moscow and St. Petersburg |
|---|---|---|
| Vercel (point of presence right in Tbilisi) | 2–39 ms | 6–87 ms |
| Türkiye | 30–99 ms | 77–95 ms |
| Poland | 71–82 ms | 42–72 ms |
| Russia, RelaxDev directly | 85–121 ms | 0–12 ms |
Vercel and Türkiye are the closest to Georgia on the network. But the network is only part of the answer.
How long to wait for the first byte of the page
| Route | from Tbilisi | from Moscow and St. Petersburg |
|---|---|---|
| Vercel | 1.56–3.54 sec | 0.46–0.67 s |
| Via a server in Turkey | 0.29–0.56 s | 0.66–1.14 s |
| Via node in Poland¹ | 0.15–0.16 s | 0.12–0.14 s |
| Russia, RelaxDev directly | 0.12–0.14 s | 0.02–0.03 s |
For comparison: a static page served by nginx directly from a Turkish server takes 0.04–0.09 s from Tbilisi. A server in Turkey is good, but only if everything lives on it at once.
¹ Measurement on another platform project, which is already working through the Polish node. The direct route to Russia was also measured there: 0.10–0.13 s from Tbilisi - the same numbers as bazariara.
The base must be next to the application
We tested this on ourselves. While the application was already working in Russia, the base remained in Neon (Frankfurt):
| Measurement | Result |
|---|---|
| Connecting to Neon from Russia | 0.8 s only to establish a connection |
| Page from Tbilisi, cold cache | 8.5–9.6 sec |
| The same page from Moscow, the cache is already warmed up | 0.13 s |
pg_dump from Neon directly to Russia | stuck, interrupted manually |
pg_dump via server in Poland | 12.6 sec |
Each page request to a database abroad paid for the journey there and back, so first we transfer the base and only then measure the speed.
Why not Türkiye
-The site on the Turkish server could not be opened from the mobile Internet in Russia.Russian mobile operators have poor connections with foreign hosting providers. We would have to separate visitors by country via GeoDNS. -The intermediate server in Turkey only adds the path:from Tbilisi the first byte is 0.29–0.56 s versus 0.12–0.14 directly, from Moscow - up to a second. -Channel “Türkiye → Russia” is unstable:connection from 0.25 to 5.2 seconds. -If you keep the application itself in Turkey, the base also moves there.It turns out a second site and a second base. -Network gain for Georgia is 30-60 ms.This is not worth a second site, second base and GeoDNS.
Why not Poland
The Polish knot is an extra shoulder on the way. For Georgia, it is slower than the direct one: the TLS handshake goes through Poland, 219–241 ms versus 106–136 directly. For visitors from Russia, it also adds a delay.Conclusion: The easiest and fastest way is to go directly to Russia, to RelaxDev. Georgia receives the first byte in 0.12–0.14 s, Russia in 0.02–0.03 s, the site and base are the same.
How we moved
1. Environment variables - with one command
npx vercel link
npx vercel env pull .env.vercel --environment=production
The entire file is imported in the “Variables” tab. The quotation marks are removed by themselves, the service VERCEL_* and TURBO_* are skipped.
Import variables from Vercel in the “Variables” tab
- Import variables from file
.env.vercel*
2. Base - import by connection string
In the “Database” tab there is an import from another database: we inserted the Neon connection string, and the data moved to the platform database. The platform itself added the connection string to the new database to the project variables.
Import database from Neon using connection string
- Import database from Neon using connection string*
3. Code - changes for a permanent server
On Vercel, each function lives separately; on RelaxDev, the application runs as one long-lived process.
| Was (under Vercel) | Became | Why |
|---|---|---|
ssl: 'require' always | SSL by connection string | Neon requires SSL, the platform base works inside the network without it |
max: 1 - one connection | default pool (up to 10) | Page requests via Promise.all occur simultaneously, not in sequence |
prepare: false | default | It was only necessary for the Neon puller |
4. DNS - via Cloudflare
The recorder .ge had a hidden Vercel record (216.198.79.1, TTL 3600) in the zone, which was not visible in the panel:
DNS gave both addresses, and some visitors ended up on the old site.
We decided to change A-records to Cloudflare. Records are in “DNS only” mode: Cloudflare only answers what address the domain has, and the traffic goes directly to the server.
A-records in Cloudflare in DNS only mode
- A-records in Cloudflare, “DNS only” mode*
What was accelerated after the move
Immediately after the move, the PR-CY score even dropped - from 87 to 79. The server began to respond faster, but in the browser the page remained heavy: 5.5 MB, of which 4.7 MB were images. Next is work on the page itself.
| What | Before | After |
|---|---|---|
| 🖼 Pictures in the catalog | originals 100–450 KB: optimization disabled due to Vercel quota | WebP 256–640 px wide via the Next.js optimizer - it’s free on your server |
| 🎞 Slider in card | the browser loaded all the photos at once: 20 cards - about 33 pictures | the second and subsequent photos are only taken when the person reaches out to the gallery |
| 📦 Product card data in HTML | ~6 KB per product: three full descriptions and service fields | ~1.2 KB: only what the card shows |
| 🏠 Home | entire catalog: 20 cards, 4 photos at once, one of them - 436 KB | first screen - text and search, sections with lazy photos below |
| ✂️ JS main | sinker catalog code - slider, cards, filters (~100 KB) | catalog - separate route, category addresses are the same |
| 🎨 CSS | two files blocked the first rendering (460 ms) | one file 8.6 KB: 170 ms only on the first visit, then from the cache on all pages |
| 💬 Support chat | in the required JS of each page | loaded after rendering |
| 📊 Google Counters and Metrics (~280 KB JS) | loaded along with the page | by the visitor’s first action or after 5 seconds; robots don't load at all |
| ♻️ Page cache | lost during cold start of functions | lives in the process until the next deployment, updated in the background |
Let's be honest about the counters: a person who closed the page in less than 5 seconds and never touched it will not be included in the statistics. This suits us.
Numbers
Service ratings
| Check | Vercel | RelaxDev |
|---|---|---|
| PageSpeed Insights, mobile | didn't measure | 100 |
| PR-CY, download speed | 87 | 100 |
| PR-CY, site analysis | didn't measure | 100 |
Result: 100 out of 100
- PageSpeed Insights, mobile - after moving*
PR-CY, speed - before optimization
- PR-CY, download speed - before optimization*
PR-CY, speed - 100
- PR-CY, download speed - after optimization*
PR-CY, site analysis - 100
- PR-CY, site analysis*
PageSpeed Insights by Category
Lighthouse 13.5, mobile profile: Moto G Power, slow 4G.
| Category | Now |
|---|---|
| Productivity | 100 |
| Accessibility | 100 |
| Recommendations | 100 |
| Search Engine Optimization | 100 |
| Agent View | 3/3 |
“Agent viewing” is a new Lighthouse category: how convenient the site is for AI agents. It checks the accessibility tree, layout stability, file llms.txt and WebMCP markup.
PageSpeed Insights - agent review 3/3
- PageSpeed Insights: Agent View - 3 out of 3*
Page load metrics /ru in PR-CY before optimization
| Metric | Vercel | RelaxDev, immediately after moving |
|---|---|---|
| Evaluation | 87 | 79 |
| First Contentful Paint | 1.1 s | 1.8 sec |
| Speed Index | 4.9 sec | 2.7 sec |
| Time to Interactive | 8.4 sec | 8.5 sec |
| Total Blocking Time | 150ms | 240ms |
| Cumulative Layout Shift | 0 | 0.01 |
| Largest Contentful Paint | — | 4.4 sec |
| Server response (TTFB) | — | 110ms |
After optimization, PR-CY sets it to 100 - screenshot above. The right column honestly shows: the move itself did not give speed in the browser. The main acceleration comes from pictures, a lightweight main counter, and delayed counters. But on Vercel this was limited by the quota for pictures and the design of the platform.
Page weight
| After the move, before optimization | Now | |
|---|---|---|
| Entire page with all files² | 5,478 KB, 84 requests | 723 KB, 58 requests |
| Pictures on page² | 4,735 KB, 43 pcs. | 179 KB, 20 pcs. |
| HTML main | 354 KB | 110 KB |
| React data inside HTML | 260 KB | 57 KB |
| Styles | 2 files, rendering blocked for 460 ms | 1 file, 8.6 KB, further from cache |
| Product picture, example | 436 KB (original) | 44 KB³ |
² PR-CY, froze before the counters were postponed: now out of these 723 KB, about 280 KB of Google and Metrica scripts are not downloaded on the first download.
³ JPEG size when requested without webp support. Browsers get webp - even easier.
Styles: inside HTML or file
Next can embed CSS directly into the page (experimental.inlineCss). At first, we did just that: a separate request for styles disappeared before the first rendering, and Lighthouse set it to 100. Then we parsed the HTML and saw that Next entered styles three times: 40 KB into the <style> tag and twice more, 40 KB into the React data. Of the 232 KB of main HTML, 121 KB were styles - in every open page.
We returned the styles as a separate file. HTML main became 110 KB instead of 232, React data - 57 KB instead of 139. The style file weighs 8.6 KB, is downloaded once per visit and then taken from the cache on all pages. This benefits visitors who view multiple pages and search robots who crawl all 928 addresses.
Price: on the very first visit, the browser waits for the style file - 170 ms on slow 4G. Because of this, in laboratory measurements the Speed Index increased from 1.3 to 2.4 s. First render and LCP did not deteriorate, and 2.4 s is still in the “green” zone.
Both measurements are PageSpeed Insights, mobile:
| Metric | Styles inside HTML | Styles as a separate file |
|---|---|---|
| First Contentful Paint | 1.1 s | 1.0 s |
| Largest Contentful Paint | 1.4 s | 1.3 s |
| Total Blocking Time | 20 ms | 30ms |
| Cumulative Layout Shift | 0 | 0 |
| Speed Index | 1.3 s | 2.4 sec |
Server response by moving stages
Globalping, page /ru, three points in each country.
| Stage | Tbilisi: first byte | Tbilisi: full answer ⁴ | Moscow and St. Petersburg: first byte | Moscow and St. Petersburg: the whole answer ⁴ |
|---|---|---|---|---|
| Vercel + Neon | 1.56–3.54 sec | 3.5–3.6 sec | 0.46–0.67 s | 0.60–0.94 s |
| RelaxDev, base still in Neon | 8.5–9.6 sec | 9.0–10.8 sec | 0.12–0.13 s | 0.19–0.23 s |
| RelaxDev + own base | 0.16–0.17 s | 0.36–0.39 s | 0.05 s | 0.11–0.13 s |
| RelaxDev after optimization | 0.12–0.14 s | 0.42 s | 0.02–0.03 s | 0.12–0.20 s |
⁴The “entire answer” includes looking up the address using DNS. In the last measurement, the domain was already on Cloudflare and was resolved again (40–150 ms), in the previous one the address was in the cache (1–30 ms).
Vercel in numbers has a cold start function. But this is exactly what a real visitor who comes in after a pause sees. Measuring points in Moscow data centers reached Vercel, but the site did not open for regular providers and mobile operators.
SEO and AI agents
Since we started focusing on speed, we also went over the technical side.
| Was | Became | |
|---|---|---|
Addresses in sitemap.xml | 30 | 928 |
| Addresses of the main page and categories in the sitemap | with a slash, each is a redirect | match canonical |
lang on English and Georgian pages | ru | en, ka |
| Tourism subcategory pages | 0 | 8: tents, barbecues, firewood, thermoses... |
llms.txt for AI assistants | last year's assortment, links to deleted sections | collected from the base |
| The name of the cart icon | no - for screen readers and agents “link without name” | there is |
| Search form | regular | marked up by WebMCP: browser agents see it as a “product search” tool |
Subcategories are a different story. For the queries “tents”, “firewood”, “barbecues”, the store was already in 3rd or 4th place, but there were no pages for these requests: the products did not have a subcategory. We listed them by product names, and eight new pages appeared with links from the main page.
What emerged after the move
The move took evening. The next days were spent on what came up later - perhaps this part is more useful than the numbers.Sitemap for 30 addresses.Next collected the sitemap once, during assembly, but there was no database during assembly. Database errors were caught silently, and only static pages remained in the sitemap - until the next build. Now it is built for each request.Stubs instead of environment variables.When building, the platform substitutes stubs of the form auto-generated-stub-for-build instead of unspecified variables, and they survive until the server starts running. Everything that begins with NEXT_PUBLIC_ is stitched into the code by Next during assembly, so the forgotten public variable ends up in the browser as a stub. Now the stubs are cleared when the server starts, and their list is written to the log: you can immediately see which variables were forgotten to be transferred from Vercel.Notifications in Telegram did not work the first time.After the move, the bot stopped sending orders and responding in the support chat. We decided in the panel: we enabled the webhook receiver and network settings for outgoing requests. One subtlety: the receiver puts a webhook with a secret from the project variables, and the site must check exactly this - at first we had 401 errors.Phone mask that was losing customers.Not about speed, but it came up in the same days. The phone field cut the insert to length and did not remove the country code: “+7 989 156-35-49” turned into “+7 798915635”, and the order did not go through. Now the number can be inserted in any way you like - with +7, with 8, with brackets - and the country is determined by the code itself.The old line prisma db push --accept-data-loss in the relaxdev.ru builder. Prisma has not been used in the code for a long time, but the line remains. While the assembly did not have access to the base, it silently fell. If we gave her access, we would bring the database into compliance with the schema, where there are no blog tables or support chat.
Summary
- ✅ The site is opening again from Russia.
- ✅ 100 out of 100 in PageSpeed Insights, in speed and in PR-CY site analysis.
- ✅ In Georgia, the server responds in 0.12–0.14 s instead of 1.5–3.5, the entire page arrives in 0.42 s instead of 3.5.
- ✅ The page is 7 times lighter: 723 KB instead of 5.5 MB, the main HTML is 110 KB instead of 354, the pictures in the catalog are 10 times lighter.
- ✅ No database limits or outdated cards.
- ✅ Sitemap has 928 addresses instead of 30.
- ✅ The whole move - one evening: variables with one command, a database based on the connection string, a couple of edits in the code.
- ✅ We do not count resource limits on Vercel and are sure that the site will be available during the holidays.
Links to measurements:
- PageSpeed Insights: pagespeed.web.dev/analysis/https-bazariara-ge-ru/xe3ssq4poa?form_factor=mobile
- PR-CY, speed: pr-cy.ru/speed_test/?device=mobile&url=https%3A%2F%2Fbazariara.ge%2Fru
- PR-CY, site analysis: a.pr-cy.ru/bazariara.ge/
- We are on VC with a focus on SEO and without technical details - vc.ru/seo/3174406-perenos-sayta-dlya-gruzii-na-relaxdev *
This is a translation of the original Russian article. Comments are on the Russian page.
