BratislavaTransfer.sk
Dvojjazyčná platforma na letiskové transfery a súkromnú prepravu. 456 nacenených trás, 100 generovaných vstupných stránok a konverzia, ktorá nesie ID skutočnej platby. Navrhol som ju, naprogramoval aj meriam.A bilingual platform for airport transfers and private transport. 456 priced routes, 100 generated landing pages and a conversion that carries the ID of a real payment. I designed it, built it and I measure it.
- RolaRole
- Dizajn, kód aj meranieDesign, code and measurement
- Stack
- .NET 8, Blazor Server, Stripe
- JazykyLanguages
- SK a EN, vlastné URLSK and EN, own URLs
- RozsahSize
- 37 000 riadkov Razor a C#37,000 lines of Razor and C#
ZadanieChallenge
Pri súkromnej preprave nestačí zákazníkovi povedať, že transfer poskytujete. Keď zadá trasu a termín, potrebuje hneď vidieť, či je preprava dostupná, koľko stojí a aké možnosti má na výber. Či ide o cestu na letisko, do mesta alebo medzi dvoma miestami.For private transportation, it is not enough to simply tell customers that a transfer service is available. When they enter a route and date, they need to immediately see whether the service is available, how much it costs, and what options they can choose from. Whether it is a trip to the airport, into the city, or between two locations.
Prezentačný web tento proces nevyrieši. Preto som postavil nacenený objednávkový systém, v ktorom zákazník vidí dostupné možnosti, vyberie si typ prepravy, prispôsobí jazdu svojim požiadavkám a objednávku dokončí online.
A presentation website does not solve this process. That is why I built a priced booking system where customers can see the available options, choose their type of transfer, customise the ride to their needs, and complete the booking online.
Stránku pre 456 trás v dvoch jazykoch nikto nenapíše ručne.Nobody writes a page for 456 routes in two languages by hand.
Cena musí byť rovnaká na vstupnej stránke, vo vyhľadávači aj v checkoute.The price has to be identical on the landing page, in the search widget and at checkout.
Bez identifikátora platby v konverzii sa nedá povedať, ktorá trasa zarába.Without the payment identifier in the conversion you cannot say which route earns.
Vstupné stránky si web generuje z cenníkaThe site generates its landing pages from the price data
Stránky na jednotlivé trasy nikto nepísal. Každá sa poskladá z jedného záznamu v dátach podľa slugu v adrese.Nobody wrote the individual route pages. Each one is assembled from a single data record by the slug in the URL.
Čas jazdy sa počíta z kilometrov trasy, do textu ho nikto nedopisuje. Title a popis vznikajú zo šablóny a z názvu trasy, takže sú pre každú trasu iné a vždy vecné.Journey time is computed from the route kilometres, nobody types it into the copy. The title and description come from a template and the route name, so they differ per route and stay factual.
Blazor, ktorý sa dá indexovaťA Blazor site that can be indexed
Aplikácia beží v režime ServerPrerendered a každá stránka má vlastný HeadOutlet. Crawler tak dostane hotové HTML aj meta tagy patriace danej stránke, nie prázdny obal, ktorý sa dopĺňa až v prehliadači.The app runs in ServerPrerendered mode and every page has its own HeadOutlet. A crawler gets fully formed HTML and the meta tags belonging to that page, not an empty shell filled in later by the browser.
Toto väčšina Blazor webov nemá vyriešené a bez toho je dynamická aplikácia pre vyhľadávač takmer prázdna.Most Blazor sites never solve this, and without it a dynamic app is close to empty for a search engine.
Štruktúrované dáta na celom webeStructured data across the site
Schéma nesedí len na jednej stránke. FAQ so 47 otázkami, drobčeková navigácia, firma, služba, otváracie hodiny aj súradnice sedia tam, kde majú, a Product s cenou je na každej stránke trasy.The schema does not sit on one page only. The 47 question FAQ, the breadcrumbs, the business, the service, opening hours and coordinates sit where they belong, and a Product with its price is on every route page.
Konverzia nesie ID skutočnej platbyThe conversion carries the ID of a real payment
Meranie som nezdedil, napísal som ho. Konverzia do Google Ads sa odosiela až po úspešnej platbe a nesie hodnotu, menu a identifikátor, ktorý má aj Stripe.I did not inherit the measurement, I wrote it. The Google Ads conversion is sent only after a successful payment and carries the value, the currency and an identifier that Stripe has too.
Ten identifikátor je ID Stripe PaymentIntentu. Konverzia, ktorú vykazuje Google Ads, a platba, ktorú naozaj prijal Stripe, majú tak spoločné číslo.That identifier is the Stripe PaymentIntent ID. So the conversion Google Ads reports and the payment Stripe actually captured share one number.
ObjednávkaThe order
Trasa, dátum, vozidlo, doplnky, zákazníkRoute, date, vehicle, extras, customer
Stripe PaymentIntent
Sumu spočíta server, objednávka ide do metadátThe server computes the amount, the order goes into the metadata
payment_intent.succeeded
Peniaze sú prijaté. Iná udalosť nič nespustí.The money is in. No other event triggers anything.
Konverzia do Google AdsConversion to Google Ads
value, currency, transaction_id
Výnos podľa trasyRevenue by route
Priradený ku kampani aj ku konkrétnej traseAttributed to the campaign and to the specific route
Meranie mimo kritickej cestyTracking off the critical path
Skripty tretích strán sa načítavajú s odkladom troch sekúnd. Na webe, ktorého pozície závisia od rýchlosti, nechcem platiť za meranie zhoršenými Core Web Vitals. Je to vedomé rozhodnutie, nie prednastavená hodnota.Third party scripts load with a three second delay. On a site whose rankings depend on speed I do not want to pay for measurement with worse Core Web Vitals. That is a deliberate decision, not a default.
Napísal som meraciu vrstvu, nie len čítal reporty z cudzieho merania. Keď klientovi nesedia konverzie v Google Ads s tým, čo mu prišlo na účet, viem, kde presne sa to láme, lebo tú istú cestu som postavil.I wrote the measurement layer, I did not just read reports from someone else's. When a client's Google Ads conversions do not match what landed in their account, I know exactly where it breaks, because I built that same path.
Objednávka žije v metadátach platbyThe order lives in the payment metadata
Celá objednávka sa zapíše do metadát Stripe PaymentIntentu. Na jej uloženie tento systém nepotrebuje databázu.The whole order is written into the metadata of the Stripe PaymentIntent. This system needs no database to store it.
Stripe má limit 500 znakov na jednu hodnotu v metadátach, preto sa objednávka pred odoslaním kráti na kusy po 450 znakoch. Webhook ich po prijatej platbe zloží späť do jednej objednávky.Stripe caps each metadata value at 500 characters, so before it is sent the order is cut into 450 character pieces. After the payment lands the webhook puts them back together into one order.
Objednávka v checkouteThe order at checkout
Rozkrájané do metadátCut into the metadata
Kúsky po 450 znakoch, aby sa zmestili pod limit Stripe.Pieces of 450 characters, to stay under the Stripe limit.
Webhook ju zloží späťThe webhook rebuilds it
Reaguje len na prijatú platbu, takže potvrdenie o rezervácii bez zaplatenia nevznikne.It reacts only to a captured payment, so a booking confirmation cannot exist without the money.
Stripe posiela každú udalosť na oba endpointy. Rozhodnúť sa musí každý web sám.Stripe delivers every event to both endpoints. Each site has to decide for itself.
Značka sedí, objednávka sa spracuje.The brand matches, the order is processed.
Cudzia objednávka, ignoruje sa.Not its order, ignored.
Prečo som to postavil taktoWhy I built it this way
Blazor Server s prerenderomBlazor Server with prerendering
Dynamická aplikácia musí byť indexovateľná, inak celý SEO program nemá zmysel. ServerPrerendered a vlastný HeadOutlet dávajú crawlerovi hotové HTML aj správne meta tagy na každej stránke.A dynamic app has to be indexable, otherwise the whole SEO programme is pointless. ServerPrerendered and a per page HeadOutlet give the crawler finished HTML and the right meta tags on every page.
Jedna služba na trasy namiesto cenovej maticeOne route service instead of a price matrix
Cena musí byť rovnaká na vstupnej stránke, vo vyhľadávači aj v checkoute. Jedna služba, ktorá číta trasy z JSON pri starte, to garantuje. Skopírovaná cenová matica to garantovať nevie.The price has to be identical on the landing page, in the search widget and at checkout. One service reading the routes from JSON at startup guarantees that. A copied price matrix cannot.
Objednávka v metadátach platby namiesto databázyThe order in payment metadata instead of a database
Objednávka existuje presne tak dlho ako platba a je uložená na tom istom mieste. Nič sa nestratí medzi dvoma systémami a nie je čo zálohovať ani synchronizovať.The order exists exactly as long as the payment does and lives in the same place. Nothing gets lost between two systems and there is nothing to back up or sync.
Meranie s odkladom, nie v kritickej cesteTracking deferred, not in the critical path
GA4, Ads aj Hotjar sa načítajú tri sekundy po obsahu. Web, ktorý stojí na organických pozíciách, nemôže platiť za meranie rýchlosťou. Merať sa dá aj bez toho, aby to bolo prvé, čo sa načíta.GA4, Ads and Hotjar load three seconds after the content. A site that lives on organic rankings cannot pay for measurement with speed. You can measure without it being the first thing to load.
Značka v metadátach na spoločnom Stripe účteA brand tag on a shared Stripe account
Dve značky na jednom účte znamenajú, že každá udalosť príde na oba endpointy. Značka v objednávke je najjednoduchší spôsob, ako zabrániť tomu, aby jednu jazdu spracovali obe strany.Two brands on one account means every event reaches both endpoints. A brand tag on the order is the simplest way to stop one ride being processed twice.
Refaktor, ktorý za to stálThe refactor that was worth it
Jedna pravda o tom, čo jazda stojí. Či ju človek vidí na vstupnej stránke, vo vyhľadávači alebo v poslednom kroku objednávky.One truth about what a ride costs. Whether you see it on a landing page, in the search widget or on the last step of the booking.
Použité technológieTechnologies used
Framework
DátaData
PlatbyPayments
SEO
AnalytikaAnalytics
KomunikáciaComms
Front end
LokalizáciaLocalization
PokrytieCoverage
Čo z toho má klientWhat a client gets out of it
Robím SEO a Google Ads. Toto je web, kde som napísal stránky, schému, checkout aj konverznú značku. Nie tri oddelené veci od troch dodávateľov, ale jeden systém.I do SEO and Google Ads. This is a site where I wrote the pages, the schema, the checkout and the conversion tag. Not three separate things from three suppliers, but one system.
Preto čítam výpadok v objednávkovom procese, integritu merania a rýchlosť stránky ako jeden problém. A keď treba niečo zmeniť v kóde, viem to zadať presne alebo to urobiť sám.That is why I read drop off in the booking flow, tracking integrity and page speed as one problem. And when something has to change in the code, I can specify it precisely or do it myself.