跳到正文
DEV Community· Landscapers.city·· 3 小時前AI 評分36

landscapers.city 如何架構涵蓋 312 個城市的本地目錄,避免建立內容單薄頁面

Structuring a 312-City Local Directory for SEO Without Creating Thin Pages

AI 導讀

landscapers.city 的美國園藝服務目錄以城市和商戶兩層 URL 架構,並要求城市頁具備足夠的活躍商戶及經核實的聯絡資料才發佈。目錄涵蓋 312 個城市,列出超過 3,153 間公司,網站地圖包含 1,222 個 URL。文章亦説明如何規劃內部連結、網站地圖及商戶資料更新。

正文

Structuring a 312-City Local Directory for SEO Without Creating Thin Pages

Local directories have a reputation problem, and most of it is earned. Many of them are thousands of near-identical pages — "Best Plumbers in {City}" — with a templated paragraph and a list of scraped names. Search engines have been explicit for years that they don't want to rank this kind of content, and users bounce from it instantly.

landscapers.city is a US directory built only for landscaping: 312 cities and more than 3,153 listed companies. This post covers the structural decisions that matter when you build a directory at that scale — URL design, what each city page needs to justify existing, internal linking, sitemaps, and a few mistakes worth avoiding. It's written for developers, because most of these decisions are made in code long before anyone writes a meta description.

1. Keep the URL model boring and flat

The site uses two levels:

/{city}-{state}              e.g. /chicago-il
/{city}-{state}/{business}   e.g. a single company profile

Plus a handful of utility pages: /estimate, /pricing, /premium, /blog.

Why flat?

  • One canonical path per entity. A city exists at exactly one URL. No /il/chicago and /chicago-il.
  • Human-readable and predictable. Users (and link builders) can guess the URL for their own city.
  • Disambiguation is built in. There are many Springfields; appending the state code solves that without a lookup table in the URL.
  • Shallow click depth. Every business is two clicks from the homepage via its city.

Slug rules worth fixing early: lowercase, ASCII only, hyphens for spaces, strip punctuation (st-louis-mo, not st.-louis-mo), and never change a slug once it's indexed without a 301.

2. A city page must earn its URL

The core question for every programmatic page: if this page didn't exist, would anyone miss it? For a city page, the honest answer is "yes" only if it contains something a user can act on.

What a useful city page carries — look at Pittsburgh or Denver for examples:

  • Real businesses with phone, address, hours and service categories.
  • Service grouping (lawn care, design, hardscaping, irrigation, tree care) so the page answers "who does X here?"
  • A clear action: call, visit a profile, or get a rough price first.
  • Local context where you actually have it — not invented "fun facts."

And what it should not do:

  • Publish a city with zero or one listing just to fill a sitemap. A thin page is worse than no page; it dilutes crawl budget and trust. Gate city publication behind a minimum-content threshold in code.
  • Spin the same paragraph with the city name swapped. If you can't say something specific, say less.

A simple publication rule:

def should_publish_city(city):
    return (
        city.active_listings >= MIN_LISTINGS
        and city.has_verified_contact_data
    )

def robots_meta(city):
    return "index,follow" if should_publish_city(city) else "noindex,follow"

noindex,follow lets you keep the page for users who arrive directly while not asking search engines to rank it.

3. Business profiles: structured data and freshness

Each company profile is the most specific, most useful page on the site. Treat it accordingly:

  • LocalBusiness schema (or a more specific subtype) with name, address, phone, opening hours and areaServed. Only mark up what's visible on the page.
  • Consistent NAP. Name, address, phone should match how the business presents itself elsewhere. Inconsistent NAP is one of the most common local-SEO problems, and directories can either help or hurt it.
  • Freshness. Free listings on landscapers.city are active for 365 days. An expiry model is a quiet SEO win: listings that nobody renews are often businesses that closed, and pruning them keeps pages accurate.
  • Handle closures with intent. When a listing goes away, either 301 to the city page or return 410. Don't leave soft-404s.

4. Internal linking as a graph, not an afterthought

At 300+ cities, internal links are your main ranking signal distribution tool. A sensible graph:

home ──► state/region hubs or top cities
city ──► every business in that city
city ──► nearby cities (by distance, not alphabet)
business ──► its city + 2–3 similar businesses
blog post ──► relevant cities and tools
tool (/estimate) ──► city pages after a result

Notes:

  • "Nearby cities" by geography is genuinely useful for users on a metro edge (someone in a suburb may be served by companies from the next city over). Compute it with a simple haversine query on city centroids.
  • Cap link lists. A footer with 312 city links on every page isn't navigation; it's noise. Show the closest or most relevant handful.
  • Contextual links from content. A blog post about irrigation in dry climates should link to cities where that's relevant, in the sentence, not in a "related links" block.

5. Sitemaps and robots: small files, big mistakes

The landscapers.city sitemap lists 1,222 URLs. At this scale one sitemap file is fine (the protocol limit is 50,000 URLs per file), but a few things are easy to get wrong:

  • Only include URLs that return 200 and are indexable. A sitemap containing 404s or noindex pages sends mixed signals. Run a nightly job that requests every sitemap URL and fails the build if any return non-200.
  • Don't block the sitemap in robots.txt. It sounds obvious; it happens. A rule like Disallow: /sitemap.xml$ can slip in during a refactor. Add a test that parses robots.txt and asserts your sitemap path is allowed.
  • Use lastmod honestly. Only bump it when page content actually changed (a new listing, updated hours), not on every deploy.
  • Split by type when it grows: sitemap-cities.xml, sitemap-businesses.xml, sitemap-blog.xml. This makes Search Console coverage reports far easier to read.

A minimal CI check:

curl -s https://example.com/sitemap.xml \
 | grep -oP '(?<=<loc>)[^<]+' \
 | xargs -n1 -P8 curl -s -o /dev/null -w '%{http_code} %{url_effective}\n' \
 | grep -v '^200' && exit 1 || echo "sitemap clean"

6. The next layer: city × service

The current structure is city and business. The obvious next dimension is service type — "hardscaping in Chicago," "irrigation in Henderson." It's also the most dangerous dimension, because it multiplies page count fast: 312 cities × dozens of service types is thousands of potential URLs.

Rules we'd apply:

  • Generate a city × service page only when enough businesses in that city actually offer that service.
  • Give it something the city page doesn't have: service-specific questions to ask a contractor, what affects price for that job, and a filtered list.
  • Canonicalize carefully so the city page and the service page don't compete for the same query.

7. Content that isn't a listing

Directories that rank long-term usually have something beyond listings. For landscapers.city that's a blog, a cost guide, and a photo-based estimate tool. Tools are particularly good for this: they're useful, they attract natural links, and they give every city page a meaningful next step.

8. Monetization that doesn't break trust

The business model matters for SEO more than people think, because it shapes what you're tempted to do to pages. landscapers.city keeps free listings free and charges $249/year for Premium + AI Page: featured placement, a badge, analytics and an AI business page whose assistant answers homeowners 24/7 and captures leads. No per-lead fees.

From a structure standpoint, the important rule is: paid placement should be labeled and shouldn't hide free listings. Featured cards at the top of a city, clearly marked, are fine. Removing non-paying businesses from the page would make the page less useful — and less deserving of ranking.

Checklist

  • [ ] Flat, stable, state-disambiguated slugs
  • [ ] Publication threshold for programmatic pages
  • [ ] LocalBusiness schema matching visible content
  • [ ] Listing expiry + proper 301/410 on removal
  • [ ] Geographic "nearby" linking, capped
  • [ ] Sitemap with only 200/indexable URLs, tested in CI
  • [ ] robots.txt test that the sitemap is allowed
  • [ ] Clear labeling of paid placement

If you've built a programmatic local site and found a better approach to the thin-page problem, share it in the comments — it's the hardest part of the whole exercise.

來源:DEV Community · dev.to