Skip to main content

Knowledge > Processes > Church Search Flow

Church Search Flow

This document describes how a visitor searches for and discovers churches on PewSearch, from the first page load through to a church detail page.


Overview

pewsearch.com ──> /directory ──> Search/Filter ──> Results Grid

click a church card

/churches/[slug]

┌────────────────┴────────────────┐
│ │
"Claim This Church" "Contact"
(if unclaimed) (if Pro Website)

Step 1: Landing on the Directory

A visitor can reach the directory from several entry points:

  • Homepage (pewsearch.com) -- clicking "Find a Church" or the navigation link.
  • Direct URL (pewsearch.com/directory).
  • State landing page (pewsearch.com/directory/texas) -- SEO-optimized page for a specific state.
  • City page (pewsearch.com/directory/texas/houston) -- churches in a specific city.
  • Denomination + state page (pewsearch.com/directory/texas/baptist-churches) -- churches of a specific denomination in a state.

The main directory page shows:

  • A hero banner with the total count of searchable churches.
  • The SmartSearchBar (search + filters).
  • Featured churches (active premium subscribers, shown at the top).
  • A paginated grid of church cards (21 per page).

Step 2: Search and Filter Options

The SmartSearchBar component provides these filter controls:

Text Search (q parameter)

  • The visitor types a church name, keyword, or phrase.
  • IF the query is exactly 2 letters and matches a US state code (e.g., "TX"), it is treated as a state filter instead of text search.
  • Text search uses PostgreSQL full-text search via the fts tsvector column on the churches table.
  • Minimum 2 characters required.

State Filter (state parameter)

  • Dropdown of all US states and Canadian provinces.
  • Selecting a state filters results to state_code = '{selected}'.

Denomination Filter (denomination parameter)

  • Dropdown organized by denomination families (Protestant, Catholic, Orthodox, etc.).
  • Uses get_denomination_values RPC to resolve canonical denomination names to all variant spellings in the database (e.g., "Catholic" matches "Roman Catholic", "Catholic Church", etc.).
  • Falls back to case-insensitive partial match if the RPC returns no results.

Theological Lens Filter (lens parameter)

  • 17 theological traditions plus a "Universal" option.
  • Uses a server-side RPC search_churches_by_lens that joins through the church_theological_lenses junction table.
  • Returns churches tagged with the selected tradition.

City Filter (city parameter)

  • Case-insensitive match on the city column.
  • Usually arrived at via a city landing page link rather than typed.

Geolocation / Nearby Search (lat, lng parameters)

  • Triggered when:
    • The visitor is in the "Pastor" persona view and shares their location, OR
    • The visitor is in the "Claim" flow and shares their location.
  • The NearbyChurchFinder component requests browser geolocation permission.
  • When coordinates are available, the search calls getNearbyChurches() instead of the standard text search.

Step 3: How the Search Query Executes

The searchChurches() function in queries.ts builds the query:

  1. Start with churches table, select listing columns.
  2. Always filter: business_status = 'OPERATIONAL' AND directory_visible = true.
    • This ensures only the ~218K visible churches appear (not the ~261K total rows including hidden/closed churches).
  3. Apply filters based on search parameters (text, state, city, denomination, lens).
  4. Sort order:
    • Primary: churches with photos first (photo_url descending, nulls last).
    • Secondary: by review count (most reviewed first).
  5. Paginate: 21 results per page using .range().
  6. Return { churches, total } where total is the exact count for pagination.

Nearby Search (Geolocation)

When lat/lng coordinates are provided, getNearbyChurches() executes instead:

  1. Calculate a bounding box: latitude +/- radiusMiles / 69, longitude +/- radiusMiles / (69 * cos(lat)).
  2. Query churches within the bounding box, filtered by OPERATIONAL and directory_visible = true.
  3. Fetch up to 3x the requested limit (to allow for distance sorting).
  4. Sort results by Euclidean distance from the user's coordinates (closest first).
  5. Return the closest N results (default 100).

This is NOT true PostGIS -- it uses a bounding-box approximation with JavaScript-side distance sorting. Accurate enough for a 25-mile radius search.


Step 4: Results Display

  1. A separate query fetches getFeaturedChurches():
    • Joins premium_churches (status = 'active') with churches.
    • Filters by directory_visible = true.
    • Applies the same search filters (state, city, denomination) in memory (small dataset).
  2. Featured churches are shown in a prominent 2-column grid at the top of results.
  3. An additional card says "Your Church Could Be Here" with a link to the claim flow.
  4. Featured church IDs are excluded from the regular grid to avoid duplicates.

Regular Church Cards

  • Displayed in a 3-column responsive grid (1 column on mobile, 2 on tablet, 3 on desktop).
  • Each ChurchCard shows:
    • Church name
    • City, state
    • Denomination
    • Star rating and review count (if available)
    • Photo thumbnail (if available)
    • Premium badge (if the church is a premium subscriber)

Pagination

  • Shows "Page X of Y" with Previous/Next links.
  • Query parameters are preserved when navigating between pages.

Persona Views

The directory supports optional persona query parameters that add contextual UI:

PersonaExtra Features
pastor"Neighborhood View" banner, geolocation nearby finder, map of nearby churches
planter"Church Planter View" banner, density map with up to 500 pins to spot gaps in coverage
leader"Denominational Intelligence" banner, bar charts showing denomination breakdown and quick stats for the selected state
visitor(default experience, no extra features)

Step 5: State and City Landing Pages

State Landing Page (/directory/[state])

  1. Validates the state slug against known US states and Canadian provinces.
  2. Fetches in parallel: church count for state, city count, top 12 cities, top 10 denominations, 6 sample churches.
  3. Displays:
    • Stats bar: total churches, cities, top denomination.
    • "About Churches in {State}" editorial content.
    • Top cities grid (clickable links to city pages).
    • Denomination and city bar charts.
    • Sample church cards.
    • "Is Your Church in {State}?" claim CTA.
  4. Full breadcrumb navigation and structured data (JSON-LD).

City Landing Page (/directory/[state]/[city-slug])

  1. Derives city name from URL slug.
  2. Fetches churches filtered by state + city, denomination breakdown, nearby cities.
  3. Displays church cards with pagination, denomination bar chart, and nearby city links.

Denomination + State Page (/directory/[state]/[denomination-slug])

  1. Detects denomination slugs using isDenominationSlug().
  2. Fetches churches filtered by state + denomination, top cities for that combination.
  3. Shows stats, city links, paginated church grid, and related links (all churches in state, denomination overview).
  4. Pre-generated at build time for combinations with 50+ churches.

Step 6: Church Detail Page (/churches/[slug])

When a visitor clicks a church card, they land on the detail page:

  1. Fetch church data from churches table by slug.
  2. Check premium status via getPremiumByChurchSlug() -- joins with premium_churches.
  3. IF the church has an active Pro Website subscription:
    • Render the full UnifiedTemplate -- a rich, denomination-aware template with video hero, staff grid, hours, events, ministries, beliefs, giving section, map, contact form, and chatbot widget.
  4. IF the church is NOT a Pro Website subscriber:
    • Render the standard detail page with:
      • Church name, denomination, address, phone, website.
      • Map (Leaflet + OpenStreetMap).
      • Hours (if available from Google scrape data).
      • About/amenities tags (accessibility, languages, parking, etc.).
      • Photo gallery (if photos exist).
      • Star rating and reviews count.
      • Nearby churches of the same denomination.
      • Nearby churches in the same city.

Calls to Action

CTAWhen shownLinks to
"Claim This Church"Church has no active premium subscription/claim/[slug]
"Contact" formChurch has Pro Website with contact form enabledInline contact form (see contact-form.md)
"Visit Website"Church has a website URL in the databaseExternal link
Cross-promo bannerAlways (non-premium churches)ChurchWiseAI product pages
Sticky bottom CTANon-premium churches (scrolls into view)/claim/[slug]
"Suggest an Update"AlwaysUpdate request form

Step 7: What Happens When No Results Are Found

  1. The results grid is replaced with an empty state:
    • Icon: church silhouette.
    • Message: "No churches found".
    • Suggestion: "Try adjusting your search terms or removing some filters."
    • "Clear all filters" button links back to /directory with no parameters.

Data Freshness

Data SourceUpdate Frequency
Church listingsScraped periodically from OpenStreetMap + Google Maps (Outscraper). New scrapes add churches; existing records are enriched.
Premium church dataUpdated in real-time by church admins through the dashboard.
Search index (fts column)Updated automatically by PostgreSQL triggers when church name/description changes.
State/city countsComputed on page load (cached 1 hour via ISR).

Caching and Performance

RouteCache Strategy
/directorys-maxage=3600 (1 hour), stale-while-revalidate=86400 (1 day)
/directory/[state]s-maxage=3600, stale-while-revalidate=86400
/directory/[state]/[slug]s-maxage=3600, stale-while-revalidate=86400
/churches/[slug]Dynamic (no cache) for non-premium, ISR for Pro Website pages

All directory queries filter by directory_visible = true, which excludes ~42K hidden churches from results.