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
ftstsvector column on thechurchestable. - 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_valuesRPC 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_lensthat joins through thechurch_theological_lensesjunction table. - Returns churches tagged with the selected tradition.
City Filter (city parameter)
- Case-insensitive match on the
citycolumn. - 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
NearbyChurchFindercomponent 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:
- Start with
churchestable, select listing columns. - Always filter:
business_status = 'OPERATIONAL'ANDdirectory_visible = true.- This ensures only the ~218K visible churches appear (not the ~261K total rows including hidden/closed churches).
- Apply filters based on search parameters (text, state, city, denomination, lens).
- Sort order:
- Primary: churches with photos first (
photo_urldescending, nulls last). - Secondary: by review count (most reviewed first).
- Primary: churches with photos first (
- Paginate: 21 results per page using
.range(). - Return
{ churches, total }where total is the exact count for pagination.
Nearby Search (Geolocation)
When lat/lng coordinates are provided, getNearbyChurches() executes instead:
- Calculate a bounding box: latitude +/-
radiusMiles / 69, longitude +/-radiusMiles / (69 * cos(lat)). - Query
churcheswithin the bounding box, filtered byOPERATIONALanddirectory_visible = true. - Fetch up to 3x the requested limit (to allow for distance sorting).
- Sort results by Euclidean distance from the user's coordinates (closest first).
- 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
Featured Churches (Premium Spotlight)
- A separate query fetches
getFeaturedChurches():- Joins
premium_churches(status = 'active') withchurches. - Filters by
directory_visible = true. - Applies the same search filters (state, city, denomination) in memory (small dataset).
- Joins
- Featured churches are shown in a prominent 2-column grid at the top of results.
- An additional card says "Your Church Could Be Here" with a link to the claim flow.
- 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
ChurchCardshows:- 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:
| Persona | Extra 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])
- Validates the state slug against known US states and Canadian provinces.
- Fetches in parallel: church count for state, city count, top 12 cities, top 10 denominations, 6 sample churches.
- 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.
- Full breadcrumb navigation and structured data (JSON-LD).
City Landing Page (/directory/[state]/[city-slug])
- Derives city name from URL slug.
- Fetches churches filtered by state + city, denomination breakdown, nearby cities.
- Displays church cards with pagination, denomination bar chart, and nearby city links.
Denomination + State Page (/directory/[state]/[denomination-slug])
- Detects denomination slugs using
isDenominationSlug(). - Fetches churches filtered by state + denomination, top cities for that combination.
- Shows stats, city links, paginated church grid, and related links (all churches in state, denomination overview).
- 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:
- Fetch church data from
churchestable by slug. - Check premium status via
getPremiumByChurchSlug()-- joins withpremium_churches. - 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.
- Render the full
- 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.
- Render the standard detail page with:
Calls to Action
| CTA | When shown | Links to |
|---|---|---|
| "Claim This Church" | Church has no active premium subscription | /claim/[slug] |
| "Contact" form | Church has Pro Website with contact form enabled | Inline contact form (see contact-form.md) |
| "Visit Website" | Church has a website URL in the database | External link |
| Cross-promo banner | Always (non-premium churches) | ChurchWiseAI product pages |
| Sticky bottom CTA | Non-premium churches (scrolls into view) | /claim/[slug] |
| "Suggest an Update" | Always | Update request form |
Step 7: What Happens When No Results Are Found
- 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
/directorywith no parameters.
Data Freshness
| Data Source | Update Frequency |
|---|---|
| Church listings | Scraped periodically from OpenStreetMap + Google Maps (Outscraper). New scrapes add churches; existing records are enriched. |
| Premium church data | Updated 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 counts | Computed on page load (cached 1 hour via ISR). |
Caching and Performance
| Route | Cache Strategy |
|---|---|
/directory | s-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.