尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
An outdoor recreation directory for the United States. Google Places data is
ingested into Firestore, and Next.js renders a static, SEO-optimised page for
every location.
• Framework — Next.js 16 (App Router), React 19, TypeScript, Tailwind CSS v4
• Data — Google Places API (New) → Firebase Firestore (Admin SDK)
• Hosting — Vercel, fronted by Cloudflare
The /places/{slug} format is load-bearing — existing allparx.com backlinks
point at these URLs, so slugify() in scripts/ingest-places.ts must not change.檢視完整 README ↗An outdoor recreation directory for the United States. Google Places data is ingested into Firestore, and Next.js renders a static, SEO-optimised page for every location.
| Route | Purpose |
|---|---|
/ | Homepage — featured categories and city index |
/places/{slug} | One location. Slug is {name}-{city}, lowercased and hyphenated |
/places | Every category |
/places/category/{type} | All locations of one category |
/cities | Every city |
/cities/{city-slug} | All locations in one city, grouped by category |
/sitemap.xml, /robots.txt, /llms.txt | Crawler entry points |
The /places/{slug} format is load-bearing — existing allparx.com backlinks
point at these URLs, so slugify() in scripts/ingest-places.ts must not change.
npm install
cp .env.example .env.local # then fill in the values
.env.local needs:
GOOGLE_PLACES_API_KEY=
NEXT_PUBLIC_FIREBASE_PROJECT_ID=
FIREBASE_CLIENT_EMAIL=
FIREBASE_PRIVATE_KEY= # quoted, with literal \n escapes
NEXT_PUBLIC_SITE_URL=https://allparx.com
The Firebase values come from the service-account JSON. That file and
.env.local are both gitignored — never commit either.
Before the first ingestion, the project needs a Firestore database. Create it
once at
console.firebase.google.com →
Firestore Database → Create database → Native mode, location nam5.
The Admin service account can read and write documents but cannot create the
database or its indexes, so this step has to happen in the console or with an
owner-level account.
Then deploy the rules and composite indexes:
npx firebase-tools deploy --only firestore
firestore.indexes.json defines the composite indexes the queries need. Two of
them are required by the ordered queries:
| Query | Index |
|---|---|
getPlacesByType | placeType ASC, rating DESC |
getPlacesByCity | city ASC, rating DESC |
Until they exist, getPlacesByType and getPlacesByCity fall back to fetching
the whole filtered set and sorting in memory. Pages still render correctly, but
each query reads far more documents than it returns — the build logs a warning
per call. Deploy the indexes to remove it.
npm run ingest:test # first 5 cities — start here
npm run ingest -- --dry-run # calls the Places API, writes nothing
npm run ingest -- --cities=10
npm run ingest -- --types=park,dog_park
npm run ingest # all 50 cities, all 18 categories
The script is idempotent: a slug that already exists is left untouched, so
re-running only fills gaps. It reads existing documents in one batched
getAll() per city+type and writes in a single batch, rather than one round
trip per place.
Cost. Each city × category pair is one Nearby Search call. The field mask
requests Enterprise-tier fields (editorialSummary, allowsDogs, restroom,
parkingOptions, goodForChildren), which bills at the higher Places API rate.
A full 50-city run is 900 calls. Check current pricing before a full run.
Place types. All 18 categories in src/lib/place-types.ts are verified
against the live API. Three types from the original spec are rejected by Google
as "Unsupported types" and were replaced:
| Original | Replacement |
|---|---|
nature_reserve | wildlife_refuge |
recreation_center | community_center |
fishing_area | fishing_pond |
If you add a type, confirm it exists in Places API Table A first — an invalid type fails every call for that type.
A static build renders one page per place plus category, city, sitemap and
llms.txt routes. Answering each of those with its own Firestore query costs
tens of thousands of document reads — enough to exhaust the free tier's
50,000/day in a single build, after which the build has no data and would
otherwise ship an empty site.
So prebuild exports the collection once to .allparx-cache/places.json, and
the data layer serves every query from that file:
npm run snapshot # runs automatically before npm run build
| Reads per build | |
|---|---|
| Per-page queries | ~40,000 |
| One snapshot | 1,321 (one per place) |
The snapshot is gitignored and regenerated on every build, including on Vercel. Without one — dev server, ISR revalidation — queries go straight to Firestore.
If the snapshot cannot be produced the build fails rather than deploying an
empty site. The exception is NOT_FOUND, which means the project genuinely has
no data yet, and builds an empty site as intended. SKIP_SNAPSHOT=1 forces the
old per-query behaviour.
At 1,321 places a build costs 1,321 reads — about 37 builds/day within the free tier. A full 50-city ingestion (~13,000 places) costs ~13,000 reads per build, which is only 3 builds/day. Move the project to the Blaze plan before ingesting all 50 cities.
npm run dev # http://localhost:3000
npm run build # production build
npm run lint
npx tsc --noEmit
Firestore reads degrade gracefully when the database or collection is empty —
next build warns and renders empty pages rather than failing. Any error other
than NOT_FOUND still propagates.
dynamic = 'force-static'), so crawlers get complete HTML with no JSWebSite + Organization site-wide,
a per-category schema.org entity (Park, Campground, RVPark,
Playground, …) with geo, openingHours, aggregateRating and
amenityFeature on listings, plus BreadcrumbList and ItemList/sitemap.xml — generated from Firestore, covering listings, categories
and cities. Google's ceiling is 50,000 URLs per file; past that, split into
generateSitemaps() chunks with an index/robots.txt — names 19 AI crawlers explicitly (GPTBot, ClaudeBot,
PerplexityBot, Google-Extended, …) so they're allowed by an unambiguous rule/llms.txt — llmstxt.org summary of what the site
holds and how its URLs are shaped, for assistants reading the site directlyPushes to main auto-deploy to Vercel. Set all five environment variables in
Project → Settings → Environment Variables before the first build — the
build reads Firestore, and FIREBASE_PRIVATE_KEY must keep its \n escapes.
scripts/ingest-places.ts Places API → Firestore ingestion
src/lib/firebase-admin.ts Admin SDK singleton
src/lib/firestore.ts Typed data access
src/lib/place-types.ts Categories, labels, schema.org mapping
src/lib/site.ts Site constants and URL helpers
src/components/ PlaceCard, PlaceGrid, JsonLd
src/app/ Routes, sitemap, robots, llms.txt, manifest
firestore.indexes.json Composite indexes
firestore.rules Client access closed; all access is server-side