尚未检测到更新。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