Tourism · SEO
Casa Grande ResortResort site built on local SEO, its own guides and real photography
A 21-URL site for a family resort in Pedro Carbo, rebuilt around real photographs of the place: pages for each search intent, a blog of long-form guides, prices that come from a single source and WhatsApp enquiries measured in GA4.
Technologies
- Next.js 16
- React 19
- TypeScript
- Tailwind CSS v4
- JSON-LD
- Google Analytics 4
- Docker + Nginx
- Railway
21
URLs in the sitemap
43
real photos of the resort
4
long-form blog guides
5
contact events in GA4
My role
Content architecture, UX/UI design, frontend development, technical SEO, GA4 measurement and deployment.
The problem
Nobody searches for a resort by name if they have not heard of it yet: they search for “day trip near Guayaquil”, “pools near Guayaquil” or “what to do with the family this weekend”. A one-page site answers none of those, and one illustrated with stock photos does not convince someone who wants to see the place before driving an hour and a half. It needed one page per intent, prices a family could add up on its own and real photographs of the resort.
What I built
- Eleven pages for the resort and its services: home, full day, lodging, activities, food, family events, weddings, gallery, about, contact and the blog index.
- Six search-intent guides: things to do as a family, weekend getaways, resorts near Guayaquil, pools, horse rides and public holidays near Guayaquil.
- A blog with four long-form guides (10 tourist spots in Guayas, 10 in Ecuador, the 9 October 2026 public holiday and company team-building trips), each with a table of contents, visible sources, an author, a review date and breadcrumbs.
- Price tables generated from the configuration: the four full-day packages, the Family Plan with lodging and the wedding package, plus what a family of four pays for a full day, calculated from those same prices.
- 43 real photographs of the resort in place of the stock ones: a gallery organised by area, mosaics on the home page and rotating photos in the hero.
- WhatsApp buttons with a different message per page and per package, which already lists the details to fill in (date, adults, children and their ages) and works without JavaScript.
- GA4 measurement of five contact events: WhatsApp, phone, map, chosen package and a click from a guide to a commercial page.
- Structured data: Resort, WebSite, BlogPosting with its ImageObject and author, BreadcrumbList, CollectionPage with ItemList, and FAQPage.
- A static export served by Nginx in Docker on Railway, with sitemap.xml, robots.txt and manifest generated from the site configuration.
How it is structured
A Next.js site exported as static files. Content and business data live in modules kept apart from the design, so a page can change its look without touching its SEO copy, and a price can be updated without hunting for it page by page.
- site.ts: contact details, navigation, full-day packages, the Family Plan and the wedding package. The menu, the tables and the JSON-LD read from it.
- seo-content.ts: titles, descriptions, headings, editorial content, sources and related links for every page and guide.
- photos.ts: one entry per photograph with its alt text, so no image is left without a description.
- whatsapp.ts: the greeting, the details to fill in and the sign-off come from the service; each button only adds its own sentence.
- Measurement: a single delegated click listener sends the WhatsApp, phone, map, package and guide events to GA4, with no personal data.
- Deployment: built in Docker with Node and run on unprivileged Nginx, with pre-compressed gzip, immutable caching for hashed files and revalidation for the HTML.
From stock photos to real photography
The first version relied on stock photos that did not show the place. The redesign replaced them with the resort's own photos, supplied by the client, without making the home page or the gallery heavy.
- 43 WebP files, one per photograph, converted from HEIC and JPG with the orientation fixed.
- Portrait photos used as hero backgrounds were cropped to 3:2.
- Seven files that repeated the same photo under another name were removed.
- A prebuild script refuses to build if an image exceeds 400 KB, if a reference is missing or if a public image goes unused.
- The gallery is organised by area: arrival and gardens, pools, weddings, horses and sports, and events and food.
Inside the site

Full day: the four packages come from the site configuration, with a WhatsApp button per package. 
A gallery organised by area, with real photographs of the resort. 
Blog index: four guides for planning day trips, holidays and events. 
Guayas guide: breadcrumbs, author, publication date and source review date.
Technical decisions
- One page per real search intent instead of a single generic landing: someone searching for horse rides and someone searching for weddings do not want the same page, even at the same resort. Weddings and family events are separate pages, each with its own quote message.
- Prices, phone numbers, packages and routes live in one module that the tables, the menu, the WhatsApp messages, the sitemap, the footer and the structured data all read from. If a price changes, it changes in one place.
- Nothing the business has not confirmed gets published: the Resort schema ships without coordinates or exact opening hours, there are no sample testimonials, and the reviews section only appears once there are real reviews.
- The blog covers are illustrations and their alt text says so: no generated image passes itself off as a photo of the resort.
- A static export on Nginx instead of a Node server: the site has no API and no database, so production holds only pre-compressed files, with long caching for hashed assets and a real 404 for routes that do not exist.
- Design rules written into the repository (no gradients, no spaced-out letters, no generic AI aesthetics) so the site looks like the place rather than like a template.
Challenges
- Moving from stock photos to the resort's own: converting HEIC and JPG originals to WebP with the orientation fixed, cropping the portrait shots used as backgrounds, and dropping seven files that repeated the same photo under another name.
- Keeping a photo-heavy home page and gallery fast: no image exceeds 400 KB, and the build stops if one does, if a reference is missing or if an image goes unused.
- Building 21 URLs around distinct intents without them competing with each other: each guide links to the service it belongs to instead of repeating the price list.
- Measuring intent without inflating it: a click on a package records one WhatsApp event and one package event, and a click is never counted as a booking.
Result
The site is in production at casagranderesort.com with its 21 URLs, its guides and its real photographs, served as static files from Railway. An HTTP checker in the repository reviews every sitemap URL: unique title and description, a single H1, canonical, internal links, structured data and images.
