Skip to main content

Service-area pages in WordPress without thin, duplicated content

Updated 4 min readMarkdown

A plumber in Dallas who also serves Plano and Irving. An accountant in Leeds with clients in Bradford and York. A cleaning company covering Toronto, Mississauga and Oshawa. They all want a page for each area, and most WordPress sites handle it badly: one template, a city name swapped in, and twenty near-identical pages that Google ignores.

Here is how we build service-area pages in WordPress so each one earns its place.

1. Decide which areas deserve a page

A page per suburb isn't a strategy. Start with areas where the business genuinely does work and can say something specific:

  • jobs completed there, with photos
  • staff or vans based nearby
  • local rules that change the work (permits, building codes, council requirements)
  • reviews from customers in that area

If you can't write three specific facts about an area, leave it on a single "Areas we serve" page instead.

2. Model areas as a custom post type

Areas are content, not settings, so give them their own post type. This keeps URLs clean (/areas/plano/) and lets editors manage them without touching the theme.

add_action( 'init', function () {
    register_post_type( 'service_area', [
        'label'        => 'Service areas',
        'public'       => true,
        'has_archive'  => 'areas',
        'rewrite'      => [ 'slug' => 'areas', 'with_front' => false ],
        'show_in_rest' => true, // needed for the block editor
        'supports'     => [ 'title', 'editor', 'excerpt', 'thumbnail', 'custom-fields' ],
        'menu_icon'    => 'dashicons-location',
    ] );
} );

Flush permalinks once after adding it (Settings, Permalinks, Save).

3. Store the facts as fields, not prose

Put the area-specific data in fields so the template can display it consistently and you can see at a glance which pages are thin:

add_action( 'init', function () {
    foreach ( [ 'area_region', 'area_jobs_count', 'area_local_note' ] as $key ) {
        register_post_meta( 'service_area', $key, [
            'type'          => 'string',
            'single'        => true,
            'show_in_rest'  => true,
            'auth_callback' => fn() => current_user_can( 'edit_posts' ),
        ] );
    }
} );

area_local_note is the important one: one or two sentences only a local would know, such as "Most homes in older parts of Irving still have galvanised pipes, so we check pressure before any fixture swap."

4. Build the template with a block pattern

In a block theme, create templates/single-service_area.html and lay it out with blocks. Use a pattern for the repeatable parts (services list, call-to-action) and leave the main content to the editor. The page should open with a short, specific answer: what you do in this area and how fast you get there.

Avoid auto-generating the body text. Templates should handle layout. People should write the words.

5. Add LocalBusiness schema once, and areaServed per page

Your business has one address. Don't invent a new one per city. Output LocalBusiness with the real address site-wide, and on each area page add areaServed:

add_action( 'wp_head', function () {
    if ( ! is_singular( 'service_area' ) ) return;
    $data = [
        '@context'   => 'https://schema.org',
        '@type'      => 'Service',
        'name'       => get_the_title() . ' services',
        'areaServed' => [ '@type' => 'City', 'name' => get_the_title() ],
        'provider'   => [ '@type' => 'LocalBusiness', 'name' => get_bloginfo( 'name' ), 'url' => home_url( '/' ) ],
    ];
    echo '<script type="application/ld+json">' . wp_json_encode( $data ) . '</script>';
} );

Link each area page from the "Areas we serve" archive and from relevant case studies or blog posts. A page reached only from a footer list of twenty cities looks like a doorway page.

7. Check for thin pages every quarter

A simple WP-CLI check flags pages with too little unique content:

wp post list --post_type=service_area --fields=ID,post_title --format=csv \
| tail -n +2 | while IFS=, read -r id title; do
  words=$(wp post get "$id" --field=post_content | wp eval 'echo str_word_count( wp_strip_all_tags( file_get_contents("php://stdin") ) );')
  echo "$title: $words words"
done

Anything under about 250 words with no local note should be improved or merged.

FAQ

Is a plugin better than a custom post type? Plugins that generate hundreds of location pages from one template produce exactly the thin content Google filters out. A custom post type with hand-written content is safer.

Should area pages target "near me" searches? No. "Near me" results depend on the searcher's location and your Google Business Profile, not on the words "near me" in your page.

How many area pages should a small business have? Usually three to ten: the places where you have real jobs, reviews and something specific to say.


We design and build WordPress sites for service businesses in the USA, UK, Canada and Australia, including service-area structures like this. See how we approach WordPress website design and development at Dzimark.

About the author

  • Dzimark is a website, mobile app and AI development company for businesses in the USA, UK, Canada and Australia: Shopify and WooCommerce stores, React and Next.js websites, iOS/Android/Flutter apps, AI chatbots, AI agents and automation. Offices in Dallas (USA) and Oshawa (Canada). https://dzimark.com/

No comments yet. Be the first to comment.