Paginate city_scores to return all 1000+ cities#3
Merged
Conversation
PostgREST caps results at 1000 rows per request; the MV currently has 1057 cities (growing). Loop with .range() until the last page returns short. City Ranks now includes every scored city instead of truncating at 1000. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
papasoft23
added a commit
that referenced
this pull request
Apr 29, 2026
…evalidate Ships items 1, 3, 4, 5 from the homepage DB-call audit. #1 - Middleware now short-circuits on public read-only paths (/, /vegan-places/*, /place/*, /city-ranks, /map, /blog/*, /recipe/*, /about, /legal/*, /sitemap, /robots.txt, plus the cached /api/home, /api/scores, /api/places/directory, /api/cities/*, /api/stats, /api/health read endpoints). Removes one supabase.auth.getUser() round-trip per request on the busiest part of the site (guests, bots, RSC prefetches). JWT is 1h with refresh-token rotation, so logged-in users keep their session - the refresh just happens on their first non-public nav. #3 - getRecentPosts / getRecentReviews now use PostgREST count aggregates (post_reactions(count), comments(count), place_review_reactions(count)) instead of selecting every joined row's id. Re-shaped to the same array-of-{id} the client expects so HomeClient is unchanged. Cuts join payload 10-50x for active posts. #4 - New platform_stats materialized view (single row) with total_places, fully_vegan, restaurants, stores, stays, sanctuaries, countries, cities. Replaces 3 queries on every /api/home cache miss (177-row directory_countries scan + directory_cities head count + sanctuaries count) with one MV lookup. Wired into existing refresh_directory_views() so the nightly cron keeps it fresh. #5 - Homepage revalidate 60s -> 300s, aligned with /api/home edge cache. Cuts homepage SSR rate ~5x; recent posts/reviews can be up to 5min stale but mutations call revalidatePath('/') when needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
papasoft23
added a commit
that referenced
this pull request
Apr 29, 2026
…, edge cache, prefetch trim Ships items 1-5 from the post-homepage audit. #3 - migration 20260429220000: partial composite indexes on places for the archived_at IS NULL hot path (idx_places_active_country_city, idx_places_active_category, idx_places_active_vegan_level, idx_places_active_country_city_rating). The previous idx_places_archived_at was filtered the wrong way (WHERE archived_at IS NOT NULL), so directory and admin queries fell back to filter-after-index. Speeds nearly every read query. #1 - GET /api/cities/followed no longer fires a write on every read. Side-effect was breaking CDN ETag/304 caching and hammering Supabase. Added POST /api/cities/followed/seen as the explicit mark-seen endpoint, ready to wire from the city-page render path when delta tracking is brought back. #2 - admin/data-quality PUT actions: - verify_vegan: DB writes parallelised with Promise.all instead of awaiting each row. ~50x faster RTT on a 50-place batch. - dismiss/removeTag: replaced the per-row update loop with a single bulk_remove_place_tag RPC (migration 20260429230000). One atomic update, no half-applied dismisses on partial failure. #4 - prefetch={false} on the dense card grids that drove RSC prefetch fan-out: HomeClient nearby places, top-cities tiles, activity feed items, page.tsx Featured Places, and CityRanksTable's popular-cities row, sortable table, and paginated card list. Hover-walks no longer blow through Vercel function quota. #5 - /api/home now sets a public, s-maxage=300, swr=3600 header on the cookieless payload (identical for every guest, safe to dedupe at the CDN) and keeps the per-user header private. Prevents cold-cache stampedes on traffic spikes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
papasoft23
added a commit
that referenced
this pull request
Apr 30, 2026
… overrides #1 Czechia -> Czech Republic merge: 326 active rows renamed via scripts/_merge-czechia.ts so the upcoming OSM CZ import lands under one canonical country name. Side discovery: refresh_directory_views was failing because platform_stats can't be REFRESHed CONCURRENTLY through a constant-expression unique index. Migration 20260430000000 switches it to plain REFRESH (1-row MV, lock is microseconds). MV state verified: Czech Republic = 326, Czechia = 0. #2 Policy block: BLOCKED_COUNTRIES = {RU, BY} exported from scripts/lib/place-pipeline.ts (the tracked layer). Used by import-osm-countries.ts (gitignored runner) for an explicit fail-loud check. Defense-in-depth - neither code is in COUNTRY_NAMES today, but if added later the policy block still fires. #3 Denmark CITY_OVERRIDES added so OSM addr:city -> canonical: - 'kobenhavn'/'koebenhavn'/'kobenhavn' -> Copenhagen (was producing the diacritic-stripped Danish form on first pass) - 'tonder kommune'/'tonder kommune' -> Tonder (strips municipality suffix the way the existing Belgian and Dutch entries do) Coverage audit scripts retained for future reuse: - _audit-coverage.ts: bucket countries by current place_count - _audit-osm-gap.ts + _audit-osm-gap-2.ts: probe Overpass per-ISO for the OSM-vs-ours gap - _audit-denmark-detail.ts: full per-city dedup preview for one country - _merge-czechia.ts + _refresh-mvs-individually.ts: pre-flight tools Denmark dry-run after all overrides: 290 OSM raw -> 247 net new after source_id dedup (36 already imported) + chain filter (6). Top cities: Copenhagen 146, Aarhus 16, Aalborg 15, Vejle 9. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
papasoft23
added a commit
that referenced
this pull request
May 6, 2026
#1 Duplicate-slug dedup - scripts/seo-1-find-dupes.ts groups places by name+city+country and flags location duplicates (coords <=80m). Keeper picked by vegan-tier > richness > unsuffixed-slug > age. Never archives a higher vegan-tier in favor of a lower one. - scripts/seo-1-archive-dupes.ts moves reviews/favorites/pack_places from loser to keeper, inserts the loser's slug into place_slug_aliases for a 301 redirect, and archives the loser with archived_reason=duplicate_of:slug. - Applied: 118 archived, 118 redirects, 0 user-content losses. #2 City-page descriptions - vegan-scene-descriptions.ts grows the generator with 4 new deterministic signals: verified vs community-tracked split, mostly-vegan callout, notable spots sentence, hours-coverage pct. No paid LLM. Per-city wording varies because the underlying numbers vary. - City page now passes verification_level, mostly_vegan count, website and hours coverage, and a quality-ranked top-picks list. #3 Indexation hygiene - Place page: robots noindex,follow when vegan_level NOT in (fully_vegan, mostly_vegan) AND no description (>=50ch) AND no image AND no website AND no hours. Audit: 3,657 of 52,627 active places (~7%) qualify; all fully/mostly-vegan rows preserved. - City page: robots noindex,follow when fewer than 5 places. Audit: 8,663 of 10,007 cities (86.6%). - Sitemap aligned with both predicates: thin.xml emits zero place URLs, priority.xml skips cities with <5 places, placeTier classifier promotes any vegan-tier or has-website row above 'thin'. OSM backfill (initial pass) - scripts/seo-4-osm-backfill.ts re-queries Overpass for places where source LIKE 'osm%' and source_id parses as osm-{node|way|relation}-N. Only writes empty fields. Self-rate-limits. - Initial 2K-candidate pass yielded 4 updates (2 desc, 1 web, 1 phone) - typical OSM nodes for vegan venues have minimal tag coverage.
papasoft23
added a commit
that referenced
this pull request
May 13, 2026
docs/SEO-PLAN-2026-05.md — top 5 SEO improvements ranked by leverage: 1. Auto-generated city-page intros for every city with >=5 places 2. Programmatic "Best vegan [category] in [city]" listicle pages 3. Country-page editorial intros (matches summer-hub treatment) 4. FAQ + PAA schema on top-N city pages 5. Internal linking pass: place -> city category -> country Each item: leverage analysis, implementation steps, effort estimate, expected impact. Ordered for execution sequence. docs/blog-drafts/ — 5 blog post drafts, 60-80% written, with YAML frontmatter (slug, target queries, title tag, meta description, internal-link plan): 01 Germany tier-2 cities comparison (gated on audit) 02 Lisbon vs top 5 European vegan cities 03 How we verify "fully vegan" (methodology / trust play) 04 24 accidentally vegan Mediterranean staples (evergreen, no blocker) 05 Why we don't take paid listings docs/blog-drafts/README.md — priority + publish cadence: this week: #4 (no blockers, highest evergreen value) week 2: #5 (trust-build, supporter conversion) week 3: #3 (methodology, authority signal) week 4: #2 (Lisbon comparison, needs live numbers) month 2: #1 (after Germany audit lands) Note on GSC: the GSC/ folder was empty (.DS_Store only), so the SEO plan is grounded in platform knowledge + the summer-hub audit from the same day, not real query data. The plan notes this and can be sharpened once a CSV is added to GSC/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
papasoft23
added a commit
that referenced
this pull request
May 14, 2026
Two pieces, both shipping now.
1. New endpoint: GET /api/export/summer-hub
Mirrors the shape of /api/export/places/<country>: returns every
vegan place across all 29 summer-hub destinations in one JSON
response. Useful for offline analysis, downstream tooling, or
caching as a static JSON.
Response shape:
{ exported_at, hub, totals, by_city: [...], places: [...] }
Each by_city entry includes count, fully_vegan_count, and
verified_fully_vegan_count alongside the per-city places list.
Cache: s-maxage=300, swr=3600.
2. Place page: "Other vegan cities in {country}" SSR block
GSC plan item #5 from docs/GSC-ACTIONS-2026-05-14.md. The
indexed/discovered ratio is currently 3.9% (3,847 / 98,399); the
bottleneck is crawl-budget, not content. Densifying internal
linking on the highest-volume route in the site (place pages)
gives Google more reason to crawl deeper.
Each place page now SSR-renders 3 sibling city links from the
same country (top 3 by place count, min 5 places per city). Pairs
with the existing "More places in {city}" sibling block: every
place page is now a 3-way crawl hub linking to its city, 3 sibling
cities, and the country page.
Audit findings recorded for visibility:
- Thin place pages already have noindex meta + are excluded from
the sitemap via placeTier(). GSC plan #1 is structurally
satisfied — no further sitemap slim needed.
- Strict-thin places (no description, no image, no review, no
vegan_level signal, no website) = 30 rows total across 52,574
alive places. Already non-indexable.
Remaining GSC plan items (#3 canonical, #4 404 redirects) need
the explicit URL lists from GSC to act on safely; they're not
in the export, so deferred until the next sync.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PostgREST caps results at 1000 rows per request. The
city_scoresMV has 1057 cities today (growing), so/api/scoresand the City Ranks page were silently truncating 57 cities. Loop with.range()until the last page returns short.🤖 Generated with Claude Code