Let's Camp SEO strategy

This document was created through a combination of consultations with programmatic SEO experts, reverse-engineering HipCamp's strategy from their sitemaps, and Let's Camp's ICP docs, investment memo, and inventory.

TL;DR

HipCamp ranks everywhere for camping-related searches in the US. Their site has somewhere between 200,000 and 500,000 pages indexed and most of them rank.

The trick is permutations. They take three lists (places, types of camping, filters) and build a page for each combination that makes sense. For example:

  • /d/united-states/california/san-diego/camping/glamping
  • /d/united-states/north-carolina/asheville/camping/cabins-and-pets
  • /state-park/united-states/pennsylvania/chapman-park/all

We can mimic this with an emphasis on Canada, where we have the strongest data. We should build pages in this order:

  1. Operator-side comparison pages. ~12 pages aimed at campground owners searching for software, not at campers searching for sites. (Not programmatic SEO but good groundwork.)
    • /software/campspot-alternative — "The best Campspot alternative for Canadian campgrounds"
    • /software/firefly-reservations-alternative
    • /software/free-campground-reservation-software
  2. Named-park pages. E.g., Algonquin, Banff, Killarney, Jasper. One page per famous park, even if they're not a customer. The search happens for the park itself.
    • /camping/ontario/parks/algonquin-provincial-park
    • /camping/alberta/parks/banff-national-park
  3. Geo + filter pages. Province + city + type of camping. Start with Saskatchewan and Alberta only, because that's where we have enough campgrounds to fill out a page.
    • /camping/saskatchewan/saskatoon/rv-sites
    • /camping/alberta/canmore/cabin-rentals
  4. Long-tail questions. The kind of thing someone types into Google at 11pm before a trip.
    • /question/is-saskatchewan-good-for-camping
    • /question/how-much-does-it-cost-to-camp-in-banff
    • /question/can-you-camp-anywhere-in-algonquin

Our moat is proprietary data — booking facts that HipCamp and Campspot literally can't see, because we have the bookings and they don't (see Appendix A).

  • Lead time: "July weekends at Algonquin sell out 47 days before check-in"
  • Live demand: "16 campers are waitlisted for sites near you right now"
  • Real price ranges: "RV sites in SK run $32-$48 in peak, $24-$36 in shoulder"
  • Recent-booking ticker: "Just booked: 3 nights at Big Bend Campground, $135 total, 2 hours ago"
  • Live events: "Live music Saturday at Big Bend Campground"
  • Historical weather: "Driest July destinations in BC", "Warmest May camping in Ontario"

How this document is organized

  1. What HipCamp is doing and at what scale
  2. What changes when you swap their ICP for Let's Camp's
  3. Infrastructure
  4. The concrete keyword and URL plan
  5. Page templates
  6. Phased rollout plan
  7. Content generation pipeline

1. What HipCamp is actually doing

There are five page archetypes stacked into one big permutation engine:

~92.5K
single-filter pages
2,615
multi-filter pages
10,748
Q&A pages
~30K+
named-entity pages
200K–500K
total PSEO index

1a. The geographic backbone

TemplateURL patternScale
Country root/d/<country>/camping/all4-5
State / province/d/<country>/<state>/camping/all104 (US+CA+AU+GB+FR)
County/d/<country>/<state>/<county>-county/camping/all290
Region / area/d/<country>/<state>/<region>/camping/all~200 named geos (Yosemite, Big Sur, Lake Berryessa)
City/d/<country>/<state>/<city>/camping/allTens of thousands (TX alone has 1,044)

These "shell" pages each show a hero, intro paragraph mentioning the place by name, listing cards, FAQ block, and internal-link rails ("Top cities", "Top regions", "Top national parks", "Nearby states").

1b. The filter dimension (48 single + 15 multi)

Stacked on top of every geo, they layer 48 single-filter pages and 15 selected multi-filter combinations. Grouped:

Show all 48 single filters with frequencies
CountSlugCategory
24,782rvAccommodation (big-3)
23,460glampingAccommodation (big-3)
20,938cabinsAccommodation (big-3)
399petsAmenity
287farm-staysVibe
286beachNatural feature
283glamping-podAccommodation
270showerAmenity
264toiletAmenity
250wifiAmenity
246forestNatural feature
244campfires-allowedAmenity
243hikingActivity
240fishingActivity
237riverNatural feature
234watersideNatural feature
231wildlife-watchingActivity
214swimmingActivity
209campervanAccommodation
209hot-tubAmenity
201horseback-ridingActivity
191wheelchair-accessAmenity
186lakeNatural feature
169yurtAccommodation
164climbingActivity
163bell-tentAccommodation
156mountainousNatural feature
140shepherds-hutAccommodation
135great-viewsVibe
123luxuryVibe
108surfingActivity
89tiny-homeAccommodation
84waterfallNatural feature
80safari-tentAccommodation
79privateVibe
74familyVibe
68ranchVibe
67vintage-trailerAccommodation
56big-rig-friendlyAmenity
55domeAccommodation
53snow-sportsActivity
45treehouseAccommodation
32caveNatural feature
22hot-springNatural feature
22desertNatural feature
21airstreamAccommodation
16a-frameAccommodation
5redwoodsNatural feature
Show all 15 multi-filter combos
CountComboNotes
271glamping-and-hot-tub
270glamping-and-pets
235glamping-and-wifi
225cabins-and-pets
221pets-and-rv
220forest-and-glamping
187glamping-and-river
172luxury-and-rv
145cabins-and-fishing
138river-and-rv
134cabins-and-river
131cabins-and-forest
127cabins-and-hot-tub
89family-and-glamping
50private-and-rv

Every combo is (glamping | cabins | rv) + (popular feature). They picked cells where a known dual-keyword search exists, not (yurt × hot-tub) or (treehouse × pets).

1c. Specific-property pages (the listing leaves)

TemplateURLUse
Private host land/land/<slug>-<id>Their actual marketplace listings (~1,000/state)
Public campground/campground/<country>/<state>/<slug>-<id>Govt-owned campgrounds, NOT bookable on HipCamp (1,325 in CA)
State park/state-park/<country>/<state>/<slug>/all2,497 nationwide
National park/national-park/<country>/<state>/<slug>/allEvery NPS site
The brilliant trick They aggregate public/government data they don't even host bookings for. The Yellowstone page exists to capture search demand, then funnels users to nearby HipCamp bookable listings via "nearby" rails.

1d. Long-tail question pages

10,748 question URLs (4,994 unique slugs after dedup). Counted by leading pattern:

1,504what-*
1,210can-*
999are-*
434where-*
296how-*
206is-*
67when-*
58does-*
44can-you-camp-anywhere-in-*
38do-*
29is-camping-allowed-in-*
28can-you-camp-in-*
23is-wild-camping-legal-in-*
23why-*
12is-there-free-camping-in-*
Show sub-pattern breakdown (what they're actually asking)

what-* dominated by activities/things to do:

  • what-are-some-popular-{X}-near-{place} (853)
  • what-is-the-best-{X}-{verb}-{place} (441)
  • what-outdoor-activities-{are/can-i}-{near/at}-{place} (219)

are-* dominated by amenity/permission about a specific place:

  • are-dogs-allowed-at-{place} (243)
  • are-pets-allowed-at-{place} (157)
  • are-there-camping-options-{near/in/at}-{place} (139)

can-* dominated by activity/permission:

  • can-i-bring-my-{pet/dog/rv}-{to/at}-{place} (327)
  • can-i-go-fishing-{at/near}-{place} (169)
  • can-i-camp-at-{place} (94)

how-* dominated by cost & access:

  • how-much-does-it-cost-to-camp-{at/in}-{place} (128)
  • how-can-i-get-{to-X} (71)
  • how-can-i-access-{place} (63)

Real corpus is ~30 master templates × thousands of entities. Each page links to 5 related questions for crawler recycling. ~50% URL duplication tolerated.

1e. Collections (editorial)

52 evergreen + event-driven landing pages: e.g., /collections/camping/memorial-day, /collections/camping/overland-expo-west, /collections/camping/field-van, etc. Low volume, high seasonal intent.

1f. The math of the index

Single-filter discover pages: ~92,500 URLs.
Multi-filter: 2,615 URLs.
City "all" pages: tens of thousands of URLs.
Public-campground pages: tens of thousands of URLs.
Question pages: 10,748 URLs.

HipCamp's PSEO index is somewhere in the 200K-500K page range.

Key insight They don't blindly make pages for every permutation. They only generate a {city, filter} page if they have ≥1 listing for that filter in that city. Most cities have 3 filter pages (rv/glamping/cabins). Largest cities have 30+. Empty filter pages would tank UX and SEO.

2. What changes for Let's Camp

Shift 1: Inventory is the bottleneck, not the keyword universe

HipCamp has tens of thousands of listings. Let's Camp has ~200 campgrounds right now.

ProvinceCustomersImplication
Saskatchewan~84 (41.8%)Dense enough to support 200+ city/filter pages
Alberta~38 (19.1%)Supports 100+ pages
Ontario~28 (13.9%)Geographically huge, ~28 grounds isn't dense
British Columbia~24 (11.9%)Same problem
All others~26 (~13%)Page-per-province at best
Critical A "Camping in Manitoba with cabins" page with two listings is worse than no page. It bounces users and Google deprioritizes thin content. Build differently for high-density (SK, AB) vs the rest.

Shift 2: There are two audiences, not one

Build both, but operator pages ship today regardless of inventory while consumer pages need inventory density + data first.

Shift 3: Canada-first taxonomy

Note This proposes a new taxonomy for Let's Camp, not a copy of HipCamp's. HipCamp's Canadian coverage is shallow — they use the same 48 US-tuned slugs (redwoods, surfing, desert) in Canada, many with near-zero search intent.

A few of HipCamp's filters are Canada-irrelevant (redwoods, desert, surfing). The additions below are Canada-specific. They live on different axes of the permutation engine — calling out which is which because they're not all the same kind of thing:

Activity filters combine with geo, e.g., /camping/saskatchewan/ice-fishing

snowmobiling ice-fishing atv / quad / ohv

Camp-style filter Canadian phrasing for accommodation type

cottage-rentals

Outranks "cabin rentals" in ON/QC search behavior.

Access-mode filter niche, applies to the territories and far north

fly-in

Operational characteristics about how the campground operates

bilingual snowbird / winter-rv indigenous-owned

Natural-feature labels can be filter slugs OR aggregate cross-province region pages

boreal-forest canadian-shield prairie-lakes

Named geographies PLACES, not filters — belong in the Geo dimension alongside Banff and Algonquin

Bay of Fundy Rockies Great Lakes

3. Infrastructure

Let's Camp actually runs three separate web properties. The main site has structural SEO friction.

PropertyStackIndexable?What's there today
letscamp.ca Vite/React SPA Yes, but slowly A javascript-heavy site that Google struggles to index.
blog.letscamp.ca WordPress + Yoast Yes 45 indexed posts. Camper-side content: tenting tips, monthly "campground booking open dates", environmental tips. Already has sitemap, schema, proper meta.
join.letscamp.ca WordPress + Yoast Yes Operator-facing marketing site: Features, Pricing, Learning Center, Owners Blog, Case Studies. Has a custom campground_review-sitemap.xml.

~1,000+ pages are indexed as of June 2026. But Google's crawler has to execute each page's JavaScript and index the rendered DOM. This is slow and expensive for Google; it's faster at indexing straight HTML. Indexing gets queued (days-to-weeks delay for new URLs), and pages can be invisible to non-Google crawlers (e.g., Bing, ChatGPT / Perplexity / Claude search, social cards).

The slowness is a real problem for a Programmatic SEO play that needs to ship hundreds of new pages quickly and earn AI-crawler citations.

The fix is adding Server-Side Rendering (SSR). There are common tools on the market to do this, such as Prerender.

Here are the benefits of SSR:

  1. Speed of indexing new pages. Rendering Javascript-heavy pages can take days to weeks for a new URL to be indexed. For a PSEO corpus shipping tens of new pages per week, that drag compounds. SSR pre-renders pages so Google's first pass can pick up all our content on the first pass.
  2. AI crawlers and non-Google search. ChatGPT, Perplexity, Claude search, and Bing don't typically render JS. AI search citations are a growing acquisition channel; SSR makes us visible to them.
  3. Deterministic reliability. Pass 2 can be silently delayed or fail. SSR is straightforward to verify.

Infra recommendations:

  1. Add Server-Side Rendering.
  2. Operator-side competitor pages — start on join.letscamp.ca immediately. WordPress + Yoast is already there. Just add ~12 new pages. (No new infra required.) Subdirectories generally outrank subdomains by 15-40% in measured A/B migrations, so eventually we'll want operator content under letscamp.ca/software/..., but the existing subdomain has accumulated authority and backlinks, so we don't need to today. Plan a consolidation pass later, once the corpus is stable and someone can babysit the 301s + Search Console for ~90 days.
  3. Consumer-side PSEO at scale → build at letscamp.ca/explore/... as a static-rendered subdirectory. Google's official position is that subdomains and subdirectories rank equivalently, but practitioner consensus (Rand Fishkin, Aleyda Solis, multiple documented migrations) is that subdirectories outperform subdomains by 15-40% in practice. The probable mechanism is authority consolidation + entity perception + denser internal linking — not crawl budget or domain authority literally. Since this is brand-new content with no migration risk, we get the upside for free by going subdirectory from day one.
  4. Noindex the 416 /camps/<slug>/terms-conditions pages. They're templated legal boilerplate with no search intent and currently sit in the sitemap. Five-minute fix, modest domain-quality lift.

Subdirectory architecture for the new PSEO surface is the consequential choice — get it right at the start.

Implementation options for letscamp.ca/explore/* — to decide with Vivek / Mike

The strategy (build /explore/ as a static-rendered subdirectory of letscamp.ca) is settled. The technical path to get there has three viable shapes. This is the kind of decision worth making jointly with whoever owns the Let's Camp tech stack:

Option A: Cloudflare Worker as a reverse proxy

A Cloudflare Worker sits in front of letscamp.ca and routes traffic by URL prefix:

Yes, this is a reverse proxy. The Worker accepts incoming requests and forwards them to different origins based on path rules. Workers run at the edge so the routing overhead is minimal (low milliseconds).

Option B: Build static rendering into the existing Vite app

Add SSR capability to the current Vite/React app, either by:

The /explore/* routes would then be pre-rendered at build time as static HTML, shipped from the same codebase as the booking app.

Option C: Add Prerender.io (or equivalent) to the existing site

A drop-in service caches a server-rendered version of every SPA page. Cloudflare detects search-bot user-agents and routes them to the cached HTML; human users get the SPA.

How to decide

A and C are complementary, not exclusive. A is the natural answer for the new PSEO surface (/explore/*). C is a separate question about the existing 1,000+ SPA pages (/camps/*) — whether to make those faster to index too. B is the long-term answer if there's appetite for an architecture consolidation.

Questions worth bringing to Vivek / Mike:

  • Is there an existing roadmap to migrate the Vite SPA to a framework (Next.js, Astro)? If yes, Option B might be the natural place to absorb this work.
  • How much eng capacity is available for new infrastructure work in the next quarter?
  • Are there reasons to keep the existing Vite app intact (mobile webview compatibility, etc.) that would steer us toward A?
  • What's the team's familiarity with Cloudflare Workers?

The strategy is sound regardless of implementation. The choice between A/B/C is about engineering trade-offs, not SEO outcomes.

4. Keyword storing — the explicit lists

The subsections below are the master taxonomy of keyword dimensions: geo, camp-style, amenity, activity, audience, and Canadian-specific niche. You generate a candidate page-set by combining one value from each relevant dimension — e.g., a province from the geo list plus a filter from the camp-style list produces a /camping/{province}/{filter} page. All values are picked with Canadian search behavior in mind, not US — that's why some of HipCamp's 48 filters from §1b don't appear here, and a few Canada-specific ones do.

3a. Geo

Country: Canada (1) · Provinces and territories (13): BC, AB, SK, MB, ON, QC, NB, NS, PE, NL, YT, NT, NU.

Tier-1 metro cities (~50 — start here)
  • BC: Vancouver, Victoria, Kelowna, Nanaimo, Kamloops, Prince George, Whistler
  • AB: Calgary, Edmonton, Banff, Canmore, Jasper, Lethbridge, Red Deer
  • SK: Saskatoon, Regina, Prince Albert, Moose Jaw
  • MB: Winnipeg, Brandon
  • ON: Toronto, Ottawa, Sudbury, Thunder Bay, North Bay, Kingston, London, Niagara Falls, Barrie, Muskoka, Algonquin
  • QC: Montreal, Quebec City, Gatineau, Sherbrooke, Trois-Rivieres, Saguenay, Mont-Tremblant
  • NB: Moncton, Fredericton, Saint John
  • NS: Halifax, Sydney, Cape Breton
  • PE: Charlottetown
  • NL: St. John's, Gros Morne
  • Territories: Whitehorse, Yellowknife, Iqaluit
Regions (~40 — huge search volume because campers think in regions)
  • BC: Okanagan, Vancouver Island, Sunshine Coast, Kootenays, Cariboo, Gulf Islands, Squamish-Whistler, Fraser Valley
  • AB: Rocky Mountains, Kananaskis, Lakeland, Crowsnest Pass, Drumheller / Badlands
  • SK: Lake Diefenbaker, Cypress Hills, Northern Saskatchewan, Qu'Appelle Valley
  • MB: Whiteshell, Interlake, Riding Mountain, Lake Winnipeg
  • ON: Muskoka, Algonquin, Bruce Peninsula, Georgian Bay, Kawartha Lakes, Thousand Islands, Northern Ontario, Prince Edward County, Haliburton
  • QC: Charlevoix, Laurentides, Eastern Townships, Mauricie, Gaspé, Saguenay-Lac-Saint-Jean
  • Atlantic: Cabot Trail, Bay of Fundy, Acadian Coast, Avalon Peninsula
Named parks (~200 — biggest organic-search magnets)

National parks: Banff, Jasper, Yoho, Kootenay, Glacier (BC), Mount Revelstoke, Pacific Rim, Gros Morne, Cape Breton Highlands, Fundy, Kejimkujik, La Mauricie, Forillon, Prince Edward Island, Riding Mountain, Wapusk, Wood Buffalo, Elk Island, Waterton Lakes, Grasslands, Prince Albert, Pukaskwa, Bruce Peninsula, Georgian Bay Islands, Point Pelee, Thousand Islands, Rouge, Mingan Archipelago, Sable Island, Torngat Mountains, Terra Nova, Kluane, Auyuittuq, and the northern parks.

Provincial park magnets: Algonquin, Killarney, Sandbanks, Bon Echo, Lake Superior, Killbear, Pinery, Awenda, Arrowhead (ON); Garibaldi, Strathcona, Manning, Cathedral, Mount Robson (BC); Cypress Hills Interprovincial, Greenwater, Duck Mountain, Meadow Lake, La Ronge (SK); Whiteshell, Hecla-Grindstone, Spruce Woods (MB); Mont-Tremblant, Gaspésie, Jacques-Cartier (QC).

3b. Camp-style — grounded in real product taxonomy

Use Let's Camp's own bookable-inventory taxonomy for filters, because every filter page can then ladder directly into the booking funnel without a category mismatch. From the homepage "What are you booking?" dropdown, the actual product categories are:

Product categoryURL slugCaptures
RV Siterv-sitesBiggest category; "RV camping in X", "RV park near Y"
Tent Sitetent-sites"Tent camping in X", "Tenting in X"
LodginglodgingCatch-all for cabin/yurt/glamping
ParkingparkingDay-use / overnight parking — niche
MooragemoorageCanada-specific niche — boat slips at waterfront campgrounds. HipCamp does NOT have this.
Rental RVrental-rvRV rentals on-site at the campground

Search-intent additions (keyword-driven, not product-driven):

SlugCapturesJustification
cabin-rentals"Cabin rental in X"Distinct keyword from "lodging"; ~10× volume in Canada
cottage-rentals"Cottage rental in X"Uniquely Canadian; outranks "cabin" in Ontario
glamping"Glamping in X"High-CTR keyword
yurts"Yurts in X"Provincial parks in ON/SK have prominent yurts
trailer-parks"Trailer parks in X"Canadian-specific phrasing for long-stay RV
seasonal-sites"Seasonal sites in X"Big in MB/SK/ON
group-camping"Group camping in X"Use-case driven
backcountry"Backcountry camping in X"Maps to parks, not direct inventory
boondocking"Boondocking in X"Camper intent; ranks adjacent

3c. Amenity dimension

petswifishowerswashroomsfull-hookups30-amp50-amppull-throughbig-rigwheelchair-accessiblewaterfrontlakefrontriversidebeachfrontoceanfrontelectricitywater-hookupsewer-hookupdump-stationplaygroundpoollaundryfirewood-included

3d. Activity dimension

fishingice-fishinghikingswimmingkayakingcanoeingboatingboat-launchmountain-bikingatvsnowmobilingcross-country-skiingbirdwatchingwildlife-viewinghorseback-ridingskiing

3e. Audience / use-case

familyfamily-friendlypet-friendlydog-friendlycouplesromanticsologroupsweddingsreunionsfirst-time-camperskids-activities

3f. Canadian-specific niche

bilingualfrancophoneindigenous-owneddark-skyfour-seasonwinter-campingsnowbirdnorthern-lightsfly-in

3g. B2B / operator-side (different intent, higher conversion)

Software comparison keywords
  • "Campspot alternative Canada"
  • "Firefly Reservations alternative"
  • "ResNexus alternative"
  • "RoverPass alternative"
  • "Campground Master alternative"
  • "RezExpert alternative"
Software intent by trait
  • "Free campground reservation software"
  • "Campground reservation software with no monthly fee"
  • "Cloud campground reservation software"
  • "Campground booking software with site map"
  • "Bilingual campground software"
  • "Campground software for small parks"
  • "RV park reservation software Canada"
  • "Campground software with marketplace"
  • "Mobile campground reservation app"
  • "Self-serve camper portal software"
Software by region & problem & how-to

By region: "Campground reservation software Ontario / Saskatchewan / Alberta / British Columbia"

By problem: "How to stop double bookings campground" · "Best way to take online campground reservations" · "Switching from Campspot to Let's Camp" · "How to refund a camper online"

How-to / glossary: "How to start a campground in Canada" · "How much does campground software cost" · "Best campground POS" · "How to price campground sites" · "Campground occupancy benchmarks"

5. Page templates

Three core templates. Each is a Mustache-ish template with slot fillers from inventory, scraped public data, and a small editor-curated per-province table.

Template A: Geo + filter (consumer)
URL:   /camping/<province-slug>/<filter-slug>     (province root)
       /camping/<province-slug>/<city-slug>/<filter-slug>   (city)
       /camping/<province-slug>/<region-slug>/<filter-slug>  (region)
H1:    "The best {filter} camping in {place}, {province}"
Meta:  "{filter} camping in {place}: {N}+ campgrounds | Let's Camp"

Above the fold:
- Hero image
- H1
- 2-3 sentence intro: sensory-specific opener about the place,
  then a one-line inventory summary ("{N}+ {filter} sites starting
  at $X/night"). NO em-dashes, NO dangling "the same".
- Filter chip rail (other available filters for this geo, with counts)

Body:
- Listing grid (cards): name, rating, # of sites, key amenity icons,
  starting price in CAD, "Book direct" CTA
- If <5 listings: pad with "Public {province} provincial parks near
  {place}" cards (info only — HipCamp's state-park trick)

Internal-link rails (this is where SEO compounds):
- "More {filter} camping in {province}" → 8 nearby cities/regions
- "Other ways to camp near {place}" → 6 other filter types
- "Top provincial parks near {place}" → 6 parks
- "Top campgrounds in {province}" → 6 most-booked listings
- Breadcrumb: Canada > {Province} > {Place} > {Filter}

Below the fold:
- FAQ (5-8 questions; LLM-generated against per-province fact sheet,
  human-edited)
- Recent reviews (pull from listings if available)
- App download / "Become a host" CTA
Template B: Named park / public-land page (highest-leverage)

Highest-value template because it captures explicit search intent ("Algonquin Park camping") AND requires zero first-party inventory.

URL:   /camping/{province-slug}/parks/{park-slug}
H1:    "Camping at {Park Name}, {Province}"
Meta:  "{Park Name} camping: {N} campgrounds nearby, booking guide,
        and what to know before you go | Let's Camp"

Body:
- 4-paragraph guide:
  1. What the park is, in one line
  2. Best time to visit, peak/shoulder seasons
  3. How camping inside the park works (fees, booking window) —
     link OUT to the official source
  4. "Where to camp nearby" — Let's Camp's actual inventory surfaces

- Cards: official park campgrounds (info only) + Let's Camp customer
  listings within X km, sorted nearest
- "Things to do at {park}" — 6-10 activities with internal links
- FAQ block
- Map embed
- Internal-link rail: other top parks in this province
Template C: Operator / B2B comparison page
URL:   /software/<comparison>   e.g. /software/campspot-alternative
H1:    "The best Campspot alternative for Canadian campgrounds"
Meta:  "Campspot alternative: free marketplace, no monthly minimum,
        Canadian support | Let's Camp"

Body:
- Hero with one-line value prop
- Side-by-side comparison table (Let's Camp vs Campspot) — cells
  straight from the VoC bad-alternatives table
- "Why owners switched" — 3-4 named-customer quotes (Nancy G.,
  Kassandra C., Michael S. — public, Capterra-sourced)
- "How the switch works" (the 48-hour migration value prop)
- FAQ: pricing, migration, training time, existing reservations
- "Book a demo" CTA throughout

A single "Campspot alternative Canada" page that ranks #1 could be worth more in pipeline than 500 thin consumer pages combined.

Template D: "When {Park} is sold out" — alternatives page (Tier A data-moat)

Tier A availability-driven, broken out as its own template because the SERP shape and page structure are distinct from Template A/B. The PAA box is reliably populated across famous parks — "Can you camp without a reservation?" / "How far in advance does {Park} sell out?" / "What's the closest park with availability?" — so the FAQ structure is templated for free.

Scope is smaller than initially scoped — only 3 of 18 famous parks have clean SERPs, 5 more are buildable with caution, and 10 are already taken by Hipcamp/Campnab. Full breakdown in VALIDATION_FINDINGS.md.

URL:   /camping/{province-slug}/parks/{park-slug}/sold-out-alternatives
       (also: /camping/alternatives-to-{park-slug})
H1:    "Where to camp when {Park Name} is sold out"
Meta:  "{Park Name} sold out? {N} nearby Canadian campgrounds with
        availability right now, plus walk-up options inside the park."

Above the fold:
- H1
- 2-sentence intro using real proprietary data, e.g.:
  "{Park} weekend sites sell out 47 days before check-in on average.
  Here are {N} Let's Camp partner campgrounds with availability
  within a 2-hour drive, plus walk-up options inside the park itself."
- "Available right now" badge or live count

Body — five blocks:
1. Live availability — nearby private campgrounds (Let's Camp inventory
   within ~2hr drive, sorted by drive time). Card per camp: name,
   distance, recent-booking ticker, starting price, "Book" CTA.
2. Walk-up campgrounds inside {Park} (info only, links to official
   park site). Same trick as HipCamp's state-park pages — capture
   intent we can't directly serve.
3. FAQ block (templated from Google's "People Also Ask" for this geo):
   - "Can you camp in {Park} without a reservation?"
   - "How far in advance does {Park} sell out?"
   - "What's the closest park with availability?"
   - "When does {Park} open for reservation?"
4. Closest similar parks — internal links to /camping/{province}/parks/{X}
   for 4-6 nearby parks with similar character (Killarney for Algonquin,
   Yoho for Banff, etc.).
5. Map: pins for nearby private campgrounds + the park itself.

Methodology block (mandatory): "Lead-time data based on {N} bookings
across {Y} Let's Camp campgrounds near {Park} between {date range}.
Refreshed weekly."

Build only for parks where the SERP is unowned — per the bulk validation in VALIDATION_FINDINGS.md.

5b. The quality bar — what makes a PSEO page worth shipping

The hard part of PSEO isn't generating pages, it's generating pages Google rewards. Empty templated pages don't just fail to rank — they degrade the perceived quality of the whole domain. Ship only if most of these are present:

Rough rule A page should be ship-worthy at 20 visits/month even if it never ranks. If it wouldn't earn a bookmark, don't publish it.

5c. The content quality rubric (every page passes this before ship)

§5b lays out the principles. This is the operational checklist that enforces them. A page can only ship if it passes this rubric. Run through it as a gate before publishing each batch.

The standalone forkable version lives at RUBRIC.md in this folder — copy it into a PR template, an Asana card, or a Linear sub-checklist for every batch.

Section 0 — SERP validation pre-flight

Before spending effort to build a page, do a quick SERP check on the target query. Some keywords look good on paper but die in the SERP for reasons that only show up when you look. If the query fails any of these, skip the page — don't generate it.

  • Vocabulary match. Google's featured snippet / top 3 results use the same noun for the thing as our URL/H1. If the snippet says "lodge" and our URL says "campground," the market vocabulary doesn't match — rewrite the framing or skip. (Real example: "snowmobiling campgrounds in northern Ontario" → market vocabulary is "sled-friendly lodges," not campgrounds. Skip or pivot.)
  • No dominant featured snippet from a strong competitor. Featured snippet from a weak source = opportunity. Featured snippet from a comparable commercial competitor = uphill.
  • No marketplace / aggregator in the top 3. HipCamp / Campspot / RoverPass in the top 3 = expensive cell. Tourism boards, Reddit, and official park sites in the top 3 = open territory.
  • Real Let's Camp inventory in the geo (≥3 customer campgrounds within the page's intent radius). Exception: Template B/D pages where the framing doesn't require inventory in the park itself.
  • People Also Ask box exists. Positive signal — Google has identified adjacent intent that doubles as your FAQ structure.

Sample-validate, don't validate everything: 5-10 keywords per template. If the template passes, ship all instances of it. If it fails, kill or pivot the template.

Validation tooling: ~90% of these checks are fully automated via bulk_serp_validation.py in this folder. It hits ScrapingBee's structured Google API at ~1 credit per query (~$0.0002) — validating ~125 candidates costs under $0.05. The one signal it can't capture reliably is AI Overview content (Google often declines to render AI Overviews for bot sessions). For high-priority candidates where AI Overview matters, run serp_html_check.py (~$0.01 per query) or screenshot manually. Full pipeline notes in CLAUDE.md in this folder.

Section 1 — Data-moat blocks ≥2 required

A PSEO page without proprietary data is a thin templated page Google eventually deprioritizes. Ship only if at least two of these are present.

  • Real price range broken out by site type, sourced from pricemodels (10th-90th percentile, not a single average)
  • Lead-time stat ("Sites near here book on average X days out") computed from bookings
  • Availability block — embedded live calendar OR "X% availability for {month}" badge from availabilities. Cache 5-15 min to protect the read path.
  • Demand signal — waitlist count, "most-booked" or "in-demand" badge from waitlists or booking velocity
  • Recent-booking ticker — "Just booked: 3 nights at {Camp}, $135 total, 2 hours ago." Anonymized (camp + price + recency only). FOMO-style proof the page is live and prices are real. None of HipCamp's competitors can show this.
  • Real reviews surfaced (mark N/A until the reviews collection ships)
  • External-data block — historical weather chart from ECCC, trail map from OSM, or attractions list

Section 2 — Structural elements 100% required

  • Unique H1 with specific entity + clear intent
  • Meta description ≤155 chars, includes entity name + key modifier
  • First 50 characters of body mention the place by name
  • Methodology block at the bottom — 2 sentences, what data + when refreshed
  • Last-updated date visible on page
  • Embedded map with pins for listings + key landmarks
  • Breadcrumb nav (Canada → Province → Region → Filter)
  • Three internal-link rails: one zoom-out (region/province) + one sibling (other places at same level) + one editorial (link to relevant blog.letscamp.ca guide or related-articles section). The editorial link is what passes WordPress-blog authority into the new corpus.
  • FAQ block with 5-8 questions answered against actual entity data

Section 3 — Schema markup 100% required

  • LodgingBusiness JSON-LD for each listing
  • FAQPage JSON-LD for FAQ section
  • BreadcrumbList JSON-LD
  • Validated in Google Rich Results Test before ship

Section 4 — Content quality ≥80% required

Editorial review. Reading-quality counts here as much as facts.

  • Each listing has a one-line differentiator ("Highest-rated waterfront site in the region" / "Bilingual EN/FR front desk")
  • No padding paragraphs added for word count
  • No em-dashes (Let's Camp copy convention)
  • No dangling references ("the same thing", "that issue", "making the switch")
  • No internal-process language ("we'll cover that on the demo")
  • Customer-facing throughout — reader's situation, reader's outcome
  • Sentence rhythm varied (no stacked same-shape sentences)
  • Numbers framed as "X+" not exact figures ("300+" not "309")
  • LLM-generated facts cross-checked against per-province fact sheet (no hallucinations)

Section 5 — Technical health 100% required

  • HTTP 200 on first crawl
  • Page loads under 2.5s LCP
  • All internal links resolve (no 404s in the batch)
  • Image alt-text on every <img> (LLM-generated against camp metadata is fine)
  • Mobile-responsive layout
  • Included in sitemap.xml with lastmod set

Section 6 — Honest pre-publish test All three yes

  • Would this page be bookmark-worthy at 20 visits/month even if it never ranks?
  • Would I be comfortable showing this to a customer campground owner?
  • Could a competitor with only public data easily replicate this? (If yes, find data they can't access.)

Pass/fail gates

SectionThresholdWhat "fail" means
0 — SERP validationAll checks passDon't generate the page; skip the template or pivot the framing
1 — Data-moat≥2 blocks presentDon't ship; rebuild with proprietary data
2 — Structural100%Don't ship; fix
3 — Schema100%Don't ship; fix
4 — Content quality≥80%Editorial rewrite before ship
5 — Technical100%Don't ship; fix
6 — Honest testAll three yesDon't ship; rethink the page

A batch only moves from staging → production when 100% of pages in the batch have passed the rubric and been human-signed-off. Pages that fail go back to the writer/template-author with the failing rows flagged. Don't try to fix them in production after they're indexed — pruning thin pages from a live corpus is much harder than not shipping them in the first place.

6. Phased rollout

Each phase has a clear gate. Headline principle: quality first, scale second. First batch (20-50 great pages across operator + consumer + park templates) is the proof point. Don't ramp until those are ranking. Then 5-10/week, not 500. Google's quality classifier penalizes sudden floods of templated pages.

PHASE 0Make the site indexable

Nothing matters until Googlebot sees HTML. Options:

  1. Static-rendered marketing surface at letscamp.ca/explore/... (Next.js static export / Astro), fronted by a Cloudflare Worker that routes /explore/* to it and passes everything else to the existing Vite SPA. Subdirectory, not subdomain — see §3 callout. Easiest path that gets the SEO architecture right. ~2-4 eng weeks.
  2. Prerender the SPA (Prerender.io, self-hosted Puppeteer). Cheapest in eng time, worst long-term.
  3. Move whole site to SSR. Best long-term, biggest project.

Pick (1) unless there's already a Next.js move in flight. Write content in parallel.

PHASE 1Operator pages (3-4 weeks)

Static, hand-written, ~12 pages. No inventory dependency. Ship dollars fastest.

  • /software/campspot-alternative
  • /software/firefly-reservations-alternative
  • /software/resnexus-alternative
  • /software/free-campground-reservation-software
  • /software/bilingual-campground-software
  • /software/cloud-campground-software
  • /software/small-campground-software
  • /software/rv-park-reservation-software-canada
  • /software/how-much-does-campground-software-cost
  • /software/how-to-start-a-campground-in-canada
  • /software/switching-from-campspot
  • /software/campground-software-comparison

Track sign-ups + demos per page. Campspot alternative should hit first — strongest commercial intent.

PHASE 2Province + filter pages (4-6 weeks)

Start narrow: Saskatchewan, then Alberta. High inventory density.

  • /camping/saskatchewan/{filter} — 15 pages
  • /camping/saskatchewan/{city}/all — top 15 SK cities with ≥2 listings
  • /camping/saskatchewan/{city}/{filter} — only if ≥1 listing
  • Same for Alberta

Roughly 100-200 pages. Then ON, BC. Skip provinces with <5 listings.

Free internal-link amplification: every Template A page gets a "Top campgrounds" rail that links to the 416 existing /camps/<slug> leaf pages already in the sitemap. Both layers compound — the new aggregation pages inherit authority from the established leaf pages, and the leaf pages get fresh internal-link sources. Same trick HipCamp uses with their state-park pages.

PHASE 3Named-park pages (4-6 weeks)

Highest-leverage template — no inventory gate.

  • All Banff/Jasper/Algonquin/Killarney national-park + major-provincial-park pages
  • Public data (park websites, Parks Canada, Ontario Parks API) supplies structured fields
  • Internal-link rail: "Private campgrounds near {Park}"

Target: 150-300 named-park pages.

PHASE 4Question pages (long-tail)

Once core matrix is ranking. Same trick HipCamp uses. Patterns:

  • is-{province}-good-for-camping
  • is-there-free-camping-in-{province}
  • can-you-camp-anywhere-in-{place}
  • cheapest-campgrounds-in-{place}
  • dog-friendly-campgrounds-in-{place}
  • {filter}-camping-near-{landmark}
  • how-much-does-it-cost-to-camp-at-{park}

Aim for 500-2,000. Cheap traffic.

PHASE 5Multi-filter combos (high-volume only)

Only after single-filter pages have stable inventory. Pick 10-15 combos with real demand:

cabins-and-pets glamping-and-hot-tub rv-and-pets cabins-and-waterfront family-and-cabins seasonal-and-rv

Don't permute everything by everything. Only generate cells with ≥1 listing.

PHASE 6+Expanded long-tail templates

Each is its own template. Tiered by data-moat strength — Tier A categories ONLY Let's Camp can do well (most defensible, ship first). Tier F is durable modifiers that compound across the corpus once data-moat templates establish ranking signal.

TIER AData-moat templates (most defensible — ship first)

Demand-revealed popularity (bookings + waitlists)

  • "Hardest-to-book campgrounds in {location}" — novel framing; high social-proof CTR
  • "Most-booked campgrounds in {province} this summer"
  • "Campgrounds that sold out for {Canada Day | Civic Holiday | Labour Day}" — annual refresh hook
  • "Quietest campgrounds in {region}" — inverse: peaceful framing
  • "Hidden-gem campgrounds in {region}" — high-rating × low-booking-velocity
  • Recent-booking ticker — not a new page, but a block embedded on every geo/filter page. "Just booked: 3 nights at {Camp}, ${total}, 2h ago." Anonymized (camp + price + recency only). FOMO-driven proof-of-life signal. Refresh every 15 min from bookings. Cache the rendered HTML to protect the read path. None of HipCamp's competitors can do this.

Availability-driven (live data from availabilities)

  • "Last-minute camping in {location} this weekend" — auto-refreshed
  • "Available campgrounds for {month} in {location}"
  • "Available {filter} for {holiday} weekend in {province}"
  • "Where to camp when {Algonquin | Banff | Cape Breton Highlands} is sold out" — captures explicit substitution intent
  • "Same-day booking campgrounds in {region}"

Lead-time intelligence (bookings.createdAt vs checkIn)

  • "How far in advance to book {Park} camping" — interactive widget + content, uniquely answerable from your data
  • "When to book {province} campgrounds for July weekends"
  • "Last-chance booking windows for {holiday} in {region}"

Pricing-driven (pricemodels + dynamicprices + longweekendrules)

  • "Cheapest campgrounds in {location}" — with real range, not made-up
  • "Campgrounds under $30 / $40 / $50 a night in {province}"
  • "Luxury / premium campgrounds in {region}"
  • "Best value RV parks in {province}" — quality-to-price index
  • "Campgrounds that don't price-gouge on long weekends in {province}" — uses longweekendrules, unique angle
  • "Monthly RV rates in {province}" — snowbird intent

Weather-driven (ECCC integration)

  • "Driest July campgrounds in {province}"
  • "Warmest May camping in {province}" — shoulder-season intent
  • "Best September camping in {province}" — fall foliage
  • "Coolest summer escapes in {region}" — heat-wave search spikes
  • "Snow-free shoulder camping in {region}"
  • "Best stargazing campgrounds in {region}" — weather × dark-sky intersection

Site-spec granularity (sites collection)

HipCamp filters at camp granularity. Let's Camp can filter at site granularity.

  • "Campgrounds that fit 40-foot RVs in {location}"
  • "50-amp service campgrounds in {province}"
  • "Pull-through site campgrounds in {region}"
  • "Big-rig friendly RV parks in {province}"
  • "Class A motorhome campgrounds near {city}"

TIER BGeographic and travel-mode

Province → region → colloquial area

Canadian colloquial regions are real search behavior: GTA, Lower Mainland, NCR, Tri-Cities, Niagara Region, Cottage Country, Eastern Townships, Cariboo, Sea-to-Sky Corridor, Annapolis Valley.

Radius-from-city (new)

  • "Camping within 1 hour / 2 hours / 3 hours of {city}"
  • "Weekend camping from {Toronto | Vancouver | Calgary}"
  • "Day-trip campgrounds from {city}"

Highway-anchored (new)

  • "Campgrounds along the Trans-Canada Highway"
  • "Campgrounds along Highway 17 (north shore Superior)"
  • "Campgrounds along the Icefields Parkway / Cabot Trail"

Travel-mode (new)

  • "Drive-up campgrounds in {region}" vs walk-in / bike-in / boat-in
  • "Boat-in / paddle-in campgrounds in {region}"

Named-landmark anchored

"Campgrounds near {landmark}" — lakes (Lake Winnipeg, Lake Louise), rivers, mountain ranges, falls.

TIER CItinerary, trail, and journey

  • Backpacking-the-trail — West Coast Trail, La Cloche Silhouette, Gros Morne Long Range, Bruce Trail. Link-bait potential, earns external links.
  • Drive-{A}-to-{B}-with-camping-stops (new) — explicit two-endpoint route planning ("Toronto to Halifax with camping stops"). Distinct from named scenic drives.
  • Road-tripping the {scenic drive} — Cabot Trail, Icefields Parkway, Highway 17, Sea-to-Sky
  • Trail-anchored basecamps (new) — "Campgrounds near {trail name}", multi-day hike basecamps. AllTrails has the trails; doesn't book campgrounds.
  • N-day itineraries"7-day Maritimes camping itinerary", "5-day Rockies camping trip"

TIER DAudience and use-case deep cuts

Use-case modifier

"{place} campgrounds for {weddings | family reunions | group retreats | corporate offsites}"

Audience deep cuts (new)

  • "Solo female-friendly campgrounds in {location}" — growing search trend, underserved
  • "ADA / wheelchair-accessible campgrounds in {location}"
  • "Adults-only campgrounds in {province}"
  • "Senior-friendly RV parks in {province}"
  • "Toddler-friendly campgrounds in {location}"
  • "First-time camper friendly campgrounds in {location}"
  • "Quiet (no parties) campgrounds in {location}"
  • "Group campgrounds for {25+ | 50+ | 100+} people in {region}"

Connectivity / lifestyle (new — remote-work era)

  • "WiFi campgrounds in {location}"
  • "Workation campgrounds in {region}" — digital nomad
  • "Off-grid campgrounds in {province}"

Length-of-stay (new — huge in Canada)

  • "Long-term RV parks in {province}" — snowbird angle
  • "Monthly RV rates in {region}"
  • "Seasonal sites in {Ontario | Manitoba | Saskatchewan}"

Pet deep cuts

"Off-leash dog campgrounds in {location}", "Campgrounds with dog parks in {location}" — deeper than generic "pet-friendly".

TIER EDecision, comparison, and utility

Comparison pages (new — high commercial intent)

  • "{Campground A} vs {Campground B}" — Algonquin vs Killarney, Banff vs Jasper
  • "Alternatives to {famous campground}" — captures priced-out / locked-out intent
  • "Less crowded than {Algonquin | Banff} campgrounds"
  • "Hidden gems near {famous park}"
  • "Camping vs cottage rental in {region}"

Reservation-utility (new)

  • "When does {Park} open for booking" — Ontario Parks 5-month window, Parks Canada calendar
  • "{Park} cancellation policy explained"
  • "Best campsites at {Park}" — site-pick guides, high evergreen traffic
  • "{Park} site map and layout"
  • "Walk-up campgrounds in {province}" — no-reservation niche

Festival / event-anchored (new)

Calgary Stampede, Pemberton Music Festival, K-Days Edmonton, Tall Ships Halifax, Cavendish Beach Music Festival. Long-tail per event; spike-driven but evergreen across years.

Map-anchored (new — Let's Camp UI strength)

  • "Interactive campground map of {region}"
  • "Map of {filter} campgrounds in {province}"

Earns external links because it's genuinely useful, not a listicle.

TIER FDurable modifier templates (compound on top once A-E rank)

All strong but lower data-moat than Tier A — they layer on the geo/filter cells already built.

  • "Best campgrounds in {place} 2026" — year modifier, refresh annually
  • "Camping guide for {place}" — long-form editorial
  • "Campgrounds open in {month} in {place}" — seasonality
  • "Year-round campgrounds in {place}" — snowbird / winter RV
  • "Campgrounds open for {holiday}" — Canada Day, Victoria Day, Labour Day, Thanksgiving
  • "{place} campgrounds that allow {tents | RVs | cabins | bonfires | dogs}"
  • "{place} campgrounds with {cabins | showers | full hookups | pool}"
  • "{place} campgrounds with {mountain | lake | river | ocean | prairie} views"
  • "Dark-sky / Aurora viewing campgrounds in {place}"
  • "First Nations / Indigenous-owned campgrounds in {place}"

Activity sub-niches (deeper than generic "{activity} in {location}")

  • "Surfing campgrounds in {BC | Nova Scotia | PEI}" — Tofino, Lawrencetown
  • "Climbing campgrounds in {Squamish | Bon Echo | Kelowna}"
  • "Mountain biking campgrounds in {Whistler | Burke Mountain | Hardwood Hills}"
  • "Whitewater rafting campgrounds near {Ottawa | Slave River}"
  • "Pickleball-friendly campgrounds in {location}" — growing, underserved

DEFERCalled out as weak

  • Trend-as-modifier (NFT-themed, AI-themed) — gimmicky, gets nuked in next quality update
  • Best campgrounds at {postal code FSA} — ~1,500 FSAs × low individual search volume = lots of work, small payoff. The Tier B radius-from-city template captures the same "near me" intent with far fewer pages
  • Camping near {airport code} — only YYZ / YVR / YYC / YEG have meaningful Canadian volume. Build 4-5, not all
  • Bachelor parties / wild weekend framing — many customer campgrounds explicitly ban large stag parties. If you build this, lean it toward "campgrounds that welcome group celebrations" — the wild-weekend angle attracts guests the customer campgrounds don't want and damages the host relationship

Full long-tail surface: ~2,000-5,000 pages stacked. Don't try to ship them all. 5-10/week ramp; pick the next batch by where keyword research shows real volume AND where data-moat blocks are available.

7. Content generation pipeline

None of these are exotic — table stakes for any PSEO play.

  1. Master inventory table. Every campground, structured fields. Sourced from MongoDB camps + sites + sitetypes (see Appendix A).
  2. Proprietary-data extract (nightly from MongoDB): aggregations of bookings, availabilities, pricemodels, waitlists, events, addons, attractions.
  3. Per-province fact sheet (one short markdown each, edited twice a year): season dates, peak/shoulder, prices, top activities. Ground truth for the LLM.
  4. Public-park dataset (quarterly): name, province, slug, official URL, geo, key features, lat/lng.
  5. External data joins:
    • Weather: Environment and Climate Change Canada open API. Per camp by lat/lng → nearest station → monthly historical normals. Powers historical weather graphs and entire pages like "Best camping months in {region}", "Driest July destinations in {province}", "Year-round warm-weather campgrounds." Refresh annually.
    • Trails: OpenStreetMap. Per camp → trails within X km → name, length, difficulty. Powers "Trails near {camp}" and the parallel "Camping near the {trail name}" page set. AllTrails has the trails; Let's Camp can be the bridge.
    • Optional: tides (DFO), ferry schedules (BC Ferries, Marine Atlantic), aurora forecast, dark-sky preserves (RASC).
  6. Templates (Mustache or Python f-strings): one per page type.
  7. LLM generation step: only state facts present in fact sheet or proprietary-data extract. Strict prompts.
  8. Human QA queue: 30-second human review before ship. 60-100 pages/hour at reasonable quality.
  9. Renderer: Next.js or Astro ingesting JSON per page.
  10. Sitemap generator: split by template (sitemap-cities.xml, sitemap-parks.xml, sitemap-software.xml), submitted via Search Console.

7b. Measurement and monitoring

PSEO is a domain-level reputation game. Dashboard should be Google Search Console (free, authoritative for what Google sees), not just Ahrefs/SEMrush.

Pre-launch / per-batch

Weekly alarms

Monthly editorial

Lead-attribution (GTM-engineer angle)

The 20% / 30-day rule If more than 20% of URLs in a batch sit in "Discovered – not currently crawled" after 30 days, stop shipping new pages until you understand why. Most reliable early-warning signal of low-quality treatment.

7c. Data freshness architecture (how often pages refresh)

A PSEO page is only useful if its data is current. But re-rendering thousands of pages on every database change is wasteful and breaks the static-rendering model. The standard pattern at SEO scale is Incremental Static Regeneration (ISR) layered with on-demand revalidation and client-side hydration — three layers, picked by how fast each kind of data changes.

Layer 1 — Static pages with time-based revalidation (the bulk of the corpus)

Pages are pre-rendered as static HTML at build time. Each page has a revalidate interval — when a request comes in after the interval has elapsed, the page is regenerated in the background and the requester gets the stale version instantly. Next.js calls this ISR; Astro does it via SSG + adapter; Vercel and Cloudflare support it natively.

Recommended intervals for Let's Camp:

Layer 2 — Webhook revalidation for material data changes

When something important changes in MongoDB (new camp listed, significant price update, new attraction added), fire a webhook from the booking app's API to the PSEO renderer. The renderer calls revalidatePath() (Next.js) or the equivalent on the specific affected URLs. The page rebuilds within seconds.

This is how you avoid the "stale until next 24h interval" gap for changes that matter. It's also why this pattern beats a database listener — events fire at the application layer when they're semantically meaningful, not on every individual database write.

Triggers worth firing:

Layer 3 — Client-side fetch for live blocks

Some data needs to be near-real-time for humans but isn't useful for Google to index. The recent-booking ticker ("Just booked 3 nights at Big Bend, 2h ago"), live waitlist counts, "open this weekend" widgets — all of these render client-side via /api/... endpoints that hit MongoDB directly.

The static page shell (Layers 1+2) is what Google indexes. The live blocks (Layer 3) layer over top after page load. Google sees the proof-of-life value via the static layers; humans get the freshness on top.

The rule that matters The HTML you ship to Googlebot should be self-sufficient. Headings, listings, prose, FAQs, price ranges, lead-time stats — all server-rendered (Layer 1 or 2). Hot data that's only meaningful for human reassurance can be client-fetched (Layer 3) because Google indexing it doesn't add ranking value anyway.

What companies actually do

Anti-patterns to avoid

Appendix A — Proprietary data → page features

Per-collection mapping: what's in each high-value MongoDB collection, what concrete page block it powers, query shape. Engineering scoping reference.

The unifying frame HipCamp and Campspot rank by aggregating shallow public data at scale. Let's Camp's edge is deep proprietary data on a focused inventory. Most pages should show 3-4 of these blocks.
TIER 1Foundational

camps

Key fields: _id, name, slug, lat/lng, province, city, description, amenityFlags, tenantId, images[], season{open,close}

Page block(s): every listing card, breadcrumb, map pin, intro paragraph. Templates: all.

sites

Key fields: campId, siteTypeId, name, attributes{length, electrical, water, sewer, maxRigSize, allowsPets, ...}

Page block(s): site-level filter ("RV sites that fit 40-foot rigs in X"), amenity callouts on listing cards.

Why it matters: HipCamp filters at camp granularity. Let's Camp can filter at site granularity. "Campgrounds in {place} with a pull-through that fits 45 feet" is a real query and Let's Camp can answer it; competitors cannot.

sitetypes + sitetemplates

Type taxonomy used across all camps. Powers the filter chip rail. Cross-check against the homepage dropdown (RV Site, Tent Site, Lodging, Parking, Moorage, Rental RV) — that's the truth source.

lodging_query

Key fields: campName, campAddress, lodgingCount · 69 records

Use: Campground-level cache showing which camps have lodging-type inventory and how many. Use to size and gate the "Lodging" / "Cabin rentals" / "Glamping" filter pages — if a province has <3 camps in this cache, don't ship a lodging filter page for that province yet.

Caveat: NOT a log of camper search queries. For real search-intent data, instrument the booking app's search bar with server-side event logging.

images / tenants / clients

images needs CDN-cached responsive sizes + alt text (LLM-generated against camp metadata). tenants/clients drive the campground-owned brand on listing pages (white-label).

TIER 2Proprietary moat

bookings

Key fields: campId, siteId, userId, createdAt, checkIn, checkOut, nights, partySize, totalCents, status

Page blocks:

  1. Lead-time intelligence — "{Place} sites book on average {X} days out; July weekends sell out by {date}." (checkIn - createdAt) percentiles, grouped by (province, month-of-checkin). Refresh weekly.
  2. "Books out by" calendar — overlay per camp. Powers the urgency CTA.
  3. Average stay length — median nights per (province, season).
  4. Party-size hints — median partySize per camp/region.

Privacy: aggregate only. No individual-booking surfacing.

availabilities

Page blocks:

  1. "Open this weekend" dynamic listing — real-time, refreshed hourly.
  2. "X% availability for {month}" badge per camp.
  3. Seasonal availability heatmap — month × availability chart on individual camp pages.

Caveat: hot read path; cache 5-15 min.

waitlists

Page block: "In demand" badge — pages where N campers are currently waitlisted. Strongest social proof Let's Camp has access to — revealed preference, not stated. Threshold ≥3 → "In demand".

pricemodels + dynamicprices + pricethresholds + seasons

Page blocks:

  1. Nightly price ranges by site type by season — "RV sites in Saskatchewan: $32-$48 peak, $24-$36 shoulder." Headlining stat on every province/region/filter page.
  2. "Most affordable in {place}" sub-list.
  3. "Premium / luxury in {place}" sub-list.

Methodology block (mandatory): "Based on actual prices at {N} Let's Camp campgrounds in {place}, as of {month YYYY}. Range reflects 10th-90th percentile."

longweekendrules

Niche but defensible: "Campgrounds with predictable long-weekend pricing in {place}" — camps that DON'T spike on holidays. Inverse marketing angle nobody else can do.

attractions

Page blocks:

  1. "Things to do near {camp}" on every camp page.
  2. Reverse: "Campgrounds near {attraction}" — entirely new page set (100-500 pages keyed off major Canadian attractions: Royal Tyrrell Museum, Wascana Centre, Bay of Fundy).
  3. "Popular activities in {region}" rolled-up tags.

Caveat: if free-text entered by owners, expect inconsistency. Cleanup pass needed.

events + eventregistrations

Page blocks:

  1. Per-camp upcoming events strip.
  2. Cross-camp event roll-ups — "Live music weekends at SK campgrounds in July 2026", "Halloween at Ontario campgrounds", "Stargazing nights at Manitoba dark-sky parks." Inherently fresh content.
  3. eventregistrations confirms which events draw — prioritize by signal not guess.

addons + addOnReport

Amenity-granular filter pages: "Campgrounds with on-site firewood in {place}", "Campgrounds with e-bike rentals in {place}", "Campgrounds with propane refill in {place}".

bookingStatsViewForChart

Pre-aggregated time-series. Probably feeds an internal dashboard. Reuse for "year-over-year booking trends in {region}" content.

TIER 3Operator-side (join.letscamp.ca)

salesReport + bookingReport

Proof stats for operator pages:

  • "Campgrounds on Let's Camp processed avg {X} bookings/site in 2025."
  • "Campgrounds that switched from Campspot saw {Y}% bookings growth in their first year" — do the analysis honestly first. Wrong stats kill the comparison page's credibility.

bookingFeeReport + detailedBookingFeeReport

Campspot savings calculator — "If your campground does $X in GMV/year, you'd save $Y vs Campspot's 10% commission." Interactive widget on /campspot-alternative.

clients + bookings (joined)

Automated case-study candidate generator. Query for customers ≥18 months on platform with growing booking volume. Surface 10 candidates per quarter; marketing picks ~3 to write up.

EXTERNALData we commit to ingesting

Environment and Climate Change Canada — historical climate

Free, government-maintained, no auth. Per camp → nearest station → 30-year monthly normals (avg/min/max temp, precipitation, snow days, frost-free days).

Page blocks: historical weather graph per camp / region / city; "best camping months in {region}" pages; "warmest July destinations in BC" modifier pages.

Refresh: yearly.

OpenStreetMap — trails

Open data. Per camp → trails within X km → name, length, surface, hiking/biking/skiing tag.

Page blocks: "Trails near {camp}", and a parallel page set "Camping near the {trail name}" (West Coast Trail, La Cloche Silhouette, Bruce Trail, Cabot Trail).

AllTrails has trails; AllTrails does NOT have campground booking. Let's Camp can be the bridge.

Optional layers (defer)

Tides (DFO) · Ferry schedules (BC Ferries, Marine Atlantic) · Aurora forecast · Dark-sky designations (RASC).

GAPSFile these as tickets

No reviews collection in the booking app

Reviews are the highest-impact PSEO block. If Let's Camp isn't capturing post-stay reviews systematically, file a product ticket: 1-question post-stay email ("How was your stay at {camp}?") with optional comment, stored as reviews (campId, userId, rating, comment, createdAt, allowPublic). Even 6 months of data unlocks the most credible content block in PSEO.

blogs collection is two abandoned posts (2020 + 2021)

noindex them or delete. If "Welcome to the New Let's Camp" has historical value, migrate to blog.letscamp.ca. Don't let stale internal-app blog pages drag domain quality.

Search-bar instrumentation

Add server-side event logging to the booking app's search bar — raw query, filters applied, results count, click-through. After 90 days you'll have the single best keyword-intent signal Let's Camp can have. Small eng task, very high downstream PSEO value.

Attraction data quality

Spot-check before relying on attractions for content. If free-text entered by owners, expect inconsistency.