migration

Elisara: from a WordPress presentation site to an Angular + Spring Boot booking platform

How we built the elisara.ro booking platform on top of a WordPress presentation site. Angular 17 SSR and Spring Boot, Stripe and Apple Pay, Telegram and Google Calendar notifications for therapists, Sentry alerts routed to Discord, with no SEO losses on the existing content.

Contents

The starting point

Until the end of May 2026, elisara.ro was a WordPress presentation site: custom theme, service pages, blog with articles. Online booking functionality: zero. Appointments were made manually through WhatsApp: the client sent a message with the desired service, someone from the Elisara booking platform replied with an available time slot, and the appointment was written down on paper or held in the memory of whoever answered.

The problems became costly as the salon grew:

  • Booking wasn't available 24/7. Appointments only came in during the hours someone was answering WhatsApp.
  • Communication errors: a time confirmed in one message, written down differently in another, two bookings for the same slot, or slots forgotten entirely.
  • Zero automated reminders. No-shows directly cost empty hours and lost revenue.
  • Payment happened exclusively on site, cash or manual transfer. Zero advance payment, zero deposit to secure serious bookings.
  • Zero reporting: impossible to know how many bookings per month, which services performed, which days had the most gaps.

The decision: build a real booking platform from scratch, on top of the existing brand and content. The marketing stays, the booking infrastructure is new.

The stack we chose

Angular 17 frontend with SSR, Spring Boot 3 backend on Java 21, Postgres, everything in Docker. The choices that made the difference:

  • Angular SSR: public pages deliver complete HTML on the first request. SEO crawlers and users on slow connections see content immediately, not a skeleton waiting for JavaScript.
  • Spring Boot 3 + Java 21: virtual threads for the availability endpoints (heavy IO against the salon's schedule), records for DTOs, a stack we maintain with the same routine as an enterprise codebase.
  • NgRx: dedicated state management for the booking flow (step 1: service, step 2: date, step 3: payment). Without it, moving through the steps with the browser's Back/Forward buttons lost context.
  • Stripe Elements + Apple Pay: direct frontend integration, for full control over UX and PCI compliance without intermediate layers.

The new services catalogue, with duration, price and live availability from the schedule, is visible at the full list of services.

The redirect map: WordPress to new routes

The old site had a few dozen URLs indexed by Google: blog articles, service pages, presentation pages. A brute-force migration (everything to the homepage or 404) would have wiped the index in two weeks and cost months of organic recovery.

We built an explicit WordPress-to-new map in nginx, loaded as a map directive:

/blog/cum-alegi-tratamentul-X     →  /ghid/cum-alegi-tratamentul-X
/servicii-faciale                 →  /services#faciale
/despre-noi                       →  /despre-noi
…

Every redirect is a permanent 301. For articles we kept the slugs exactly, only the prefix changed from /blog/ to /ghid/. See the new Elisara guides section, which inherits the SEO authority of the old blog.

Small but real snag: we needed map_hash_bucket_size 128; in nginx because some old WordPress slugs exceeded the default of 64 and the configuration failed to validate. The kind of detail that surfaces at nginx -t during pre-flight, not at cutover.

Payment: Stripe Live + Apple Pay + SmartBill invoicing

Stripe Elements exposes a unified flow: regular card, Apple Pay, Google Pay, all through the same Payment Intent. The steps that cost real time:

  1. Apple Pay domain verification: the apple-developer-merchantid-domain-association file must be served from /.well-known/ over valid HTTPS, and Stripe revalidates it automatically. A path mistake (/static/ instead of /.well-known/) blocks the Apple Pay launch for full days after go-live.
  2. The subscriptions module: recurring clients prefer packages (pre-paid sessions, automatic renewal) over individual bookings. These are now visible at elisara.ro/abonamente and use Stripe Subscriptions.
  3. SmartBill invoicing: once the payment goes through, a Spring Boot webhook creates the invoice in SmartBill (e-Factura compatible) and attaches it to the receipt emailed to the client. Zero manual effort for the salon.

The implicit cost for the business stays the same: Stripe commission plus SmartBill subscription. The gain: zero manual invoicing overhead and Apple Pay available from day 1.

Integrations that keep the system alive

A booking site doesn't stop at "click and you've paid". Behind a flow that looks simple there's a chain of notifications and alerts that has to work at 3am too. What's stitched into Elisara now:

  • Email + SMS for clients: immediate confirmation after booking, a reminder 24 hours before the appointment, notification on rescheduling or cancellation. Email goes out through our own SMTP, with DKIM and SPF configured correctly so it doesn't land in spam. SMS uses a local provider for good deliverability on Romanian networks.
  • Telegram for therapists: each therapist links her Telegram chat through a token, once. After that, every new booking appears instantly as a message with the client's name, the service, the time and a confirmation option. Two-way: the therapist replies with simple commands (/agenda, /azi) directly from Telegram, without opening the admin panel.
  • Google Calendar sync for therapists: each therapist authorises her Google account once, through OAuth. After that, every new booking automatically appears as an event in her Google Calendar, with the title, time, duration and client. Schedule visibility lands directly in the calendar she already works with daily, without having to check a second system.
  • Sentry for backend errors: any exception in Spring Boot is captured automatically, grouped by stack trace signature (a thousand manifestations of the same bug become a single issue) and includes the request context (URL, method, user). Routed to crawlerra's Discord, where our team sees the alert within a minute.
  • A second alerting layer: a script that watches the backend log every 5 minutes and posts to Discord if suspicious patterns show up (timeouts, uncaught exceptions). A safety net for the case where Sentry itself has problems.
  • Internal monitoring dashboard: live metrics for payment success rate, channel distribution (card / Apple Pay / Google Pay), daily booking volume, therapist utilisation and active database connections. Visible only to the admin team.

Operationally, it means a production error triggers an alert in our team's chat within 1 to 5 minutes. For the salon, it means it doesn't lose bookings to a bug nobody can see.

The cutover: DNS flip, certificate, short window

Cutover scheduled for Saturday night, 31 May 2026. The steps, in order:

  1. T-30 min: snapshot of the WordPress database and the /opt/elisara directory on the server.
  2. T-0: DNS A record changed from the old IP to the new IP. TTL reduced to 60 seconds a day before, so propagation would be fast.
  3. T+2 min: most traffic already resolved to the new IP. Let's Encrypt HTTP-01 challenge run via --webroot. We accept a short window of certificate mismatch (a few minutes) as a tradeoff against DNS-01, which is more complicated and needs API access at the DNS registrar.
  4. T+5 min: certificate OK, Apple Pay verifies .well-known/, live payments tested with a real card (1 RON, immediate refund).
  5. T+15 min: the whole stack verified, the switch declared final.

A rollback plan was documented beforehand (DNS back to WordPress, snapshot restore), but it wasn't needed.

What's live now

Since 31 May 2026, elisara.ro has been running on the new stack. What's working, measurably:

  • Fast SSR on every public route.
  • Stripe Live + Apple Pay, both tested with real payments before go-live.
  • SmartBill issues the invoice automatically on every successful payment.
  • Old URLs redirected 301 to their new equivalents, with no drop observed in Search Console in the first week.
  • The salon adds new services from the admin panel, without a developer. Bookings 24/7, optional online payment, automatic reminders, all integrated into the therapists' calendars.

If you want to see the result in production, book directly at elisara.ro. The booking flow is designed not to ask for an account or password for a single visit.

Frequently asked questions

How long did the Elisara migration take?

Development ran in parallel with the live WordPress site for several months, and the actual cutover took under 20 minutes. The whole new stack was built, tested and pre-deployed on an internal domain (dev.elisara.ro) before cutover, to eliminate any surprises at the DNS flip. The actual cutover (snapshot, DNS, certificate, Apple Pay verify, smoke test) was a short window at night.

How did we avoid losing SEO during the migration?

An explicit map of 301 redirects in nginx, slugs kept identical for articles, and content migrated 1:1. Every old URL indexed by Google was mapped to its new equivalent before cutover, validated with nginx -t, then tested manually with curl -I for every critical slug. In the first week after go-live we didn't observe any drop in organic traffic in Search Console.

Why Angular SSR and not Next.js or Astro?

Because we run Angular and Spring Boot enterprise every day, and the booking flow has enough state complexity that NgRx was worth the cost. Next.js and Astro are excellent options for React-first or content-first projects. For Elisara, the choice came down to stack coherence in the hands of the same team that maintains it: the same typed language end to end (TypeScript on the front, Java records on the back) and the same release discipline we apply for enterprise clients.