Knowledge > Products > PewSearch Directory > Church Detail Page
Church Detail Page
What It Is
The church detail page is the single most important page in PewSearch. It is the canonical URL for every church in the directory -- the page that ranks in Google, the page visitors land on when searching for a specific church, and the page where the claim/upgrade funnel begins.
Route: /churches/[slug]
File: pewsearch/web/src/app/churches/[slug]/page.tsx
Each of the 218K+ visible churches has a detail page. These pages are statically generated at build time for high-traffic churches and rendered on-demand for the long tail.
Data Displayed
The detail page renders everything known about a church, organized into visual sections:
Header Section
| Element | Source | Notes |
|---|---|---|
| Church name | church.name | H1 tag, primary heading |
| Denomination badge | church.denomination | Colored badge, links to denomination page |
| Premium badge | church.is_premium | Gold verified badge if claimed |
| Rating stars | church.rating | 1-5 stars from Google Maps |
| Review count | church.reviews_count | "(X reviews)" label |
| Photo | church.photo_url | Hero image, falls back to placeholder |
| Logo | church.logo_url | Circular overlay on hero (if available) |
Contact & Location Section
| Element | Source | Notes |
|---|---|---|
| Full address | church.address | With copy-to-clipboard |
| Phone number | church.phone | Click-to-call link |
| Website | church.website | External link (opens new tab) |
| Google Maps link | church.google_maps_url | "Get Directions" button |
| Interactive map | church.latitude, church.longitude | Leaflet map with pin |
Service Times Section
| Element | Source | Notes |
|---|---|---|
| Working hours | church.working_hours | JSONB: {"Sunday": ["9:00 AM", "11:00 AM"], "Wednesday": ["7:00 PM"]} |
| Custom hours (Premium) | premium.custom_hours | Overrides working_hours when available |
| Next service highlight | Computed from hours | "Next service: Sunday at 9:00 AM" callout |
The hours display logic:
pseudocode: resolveHours(church, premium)
if premium exists AND premium.custom_hours is not empty:
return premium.custom_hours // Pastor-provided hours take priority
else if church.working_hours is not empty:
return church.working_hours // Google Maps scraped hours
else:
return null // Show "Hours not available" message
About Section
The church.about JSONB field contains up to ~20 metadata fields scraped from Google Maps:
| Field | Type | Example |
|---|---|---|
accessibility | JSONB | {"Wheelchair accessible entrance": true, "Wheelchair accessible parking": true} |
amenities | JSONB | {"Restroom": true} |
atmosphere | JSONB | {"LGBTQ+ friendly": true} |
crowd | JSONB | {"Groups": true} |
from_the_business | JSONB | {"Identifies as women-led": true} |
highlights | JSONB | {"Great for kids": true} |
offerings | JSONB | {"Groups": true, "Youth programs": true} |
payments | JSONB | {"Debit cards": true} |
planning | JSONB | {"Appointment required": false} |
popular_for | JSONB | {"Worship services": true} |
service_options | JSONB | {"Online services": true, "Onsite services": true} |
Each non-empty field renders as a collapsible section with icon badges.
Photos Grid
| Element | Source | Notes |
|---|---|---|
| Primary photo | church.photo_url | Large hero display |
| Logo | church.logo_url | Circular display |
| Additional photos | Premium uploads | Only for Premium subscribers |
Nearby Churches
A "Nearby Churches" section shows churches within a configurable radius:
pseudocode: getNearbyChurches(lat, lng, radiusMiles=10, limit=6)
SELECT id, name, slug, address, city, state_code, photo_url,
denomination, rating, reviews_count,
(haversine_distance(lat, lng, church.latitude, church.longitude)) as distance
FROM churches
WHERE directory_visible = true
AND business_status = 'OPERATIONAL'
AND distance <= radiusMiles
AND id != current_church_id
ORDER BY distance ASC
LIMIT limit
This uses PostGIS-compatible distance calculation. The query runs server-side and returns the nearest 6 churches.
Claim CTA
Every non-premium church shows a sticky "Claim This Church" call-to-action:
| State | CTA Text | Link |
|---|---|---|
| Unclaimed | "Is this your church? Claim it for free." | /claim/[slug] |
| Claimed (free) | "Upgrade to Premium" | /pricing |
| Premium | No CTA (already subscribed) | -- |
The CTA is sticky at the bottom of the page on mobile and appears in the sidebar on desktop. It is the primary conversion point for the PewSearch funnel.
Pastor Lead Capture
Below the hours section, a contextual CTA targets pastors:
"Are you the pastor of [Church Name]?"
"Claim your free listing to update hours, add photos, and connect with visitors."
[Claim This Church →]
This is distinct from the sticky CTA and appears inline in the content flow.
Vanity URL Support
Premium churches can set a vanity_slug in premium_churches. When a vanity slug is configured, the church is accessible at both:
/churches/[original-slug](canonical)/churches/[vanity-slug](redirect to canonical)
The vanity slug lookup happens in the page's data fetching:
pseudocode: resolveChurchBySlug(slug)
// First try direct match
church = query churches WHERE slug = slug
if church exists:
return church
// Then try vanity slug
premium = query premium_churches WHERE vanity_slug = slug
if premium exists:
church = query churches WHERE id = premium.church_id
redirect to /churches/[church.slug] // 301 redirect
// Not found
return 404
SEO Implementation
JSON-LD Structured Data
Every church detail page emits JSON-LD structured data for Google:
{
"@context": "https://schema.org",
"@type": "Church",
"name": "Grace Community Church",
"address": {
"@type": "PostalAddress",
"streetAddress": "123 Main St",
"addressLocality": "Dallas",
"addressRegion": "TX",
"postalCode": "75201"
},
"telephone": "+1-214-555-0100",
"url": "https://www.gracecommunity.org",
"openingHoursSpecification": [
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": "Sunday",
"opens": "09:00",
"closes": "12:00"
}
],
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.7",
"reviewCount": "142"
},
"geo": {
"@type": "GeoCoordinates",
"latitude": 32.7767,
"longitude": -96.7970
}
}
Breadcrumb Schema
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{"@type": "ListItem", "position": 1, "name": "Home", "item": "https://pewsearch.com"},
{"@type": "ListItem", "position": 2, "name": "Directory", "item": "https://pewsearch.com/directory"},
{"@type": "ListItem", "position": 3, "name": "Texas", "item": "https://pewsearch.com/directory?state=TX"},
{"@type": "ListItem", "position": 4, "name": "Grace Community Church"}
]
}
Meta Tags
| Tag | Template |
|---|---|
<title> | `{name} - {city}, {state} |
meta description | {name} in {city}, {state_code}. {denomination}. Service times, directions, contact info, and more. |
og:title | Same as <title> |
og:description | Same as meta description |
og:image | church.photo_url or PewSearch default OG image |
canonical | https://pewsearch.com/churches/{slug} |
Church Type Definition
The full type returned by getChurchBySlug() in queries.ts:
interface Church {
id: string;
name: string;
slug: string;
address: string;
street: string | null;
city: string;
state: string;
state_code: string;
zip_code: string | null;
latitude: number | null;
longitude: number | null;
phone: string | null;
website: string | null;
category: string | null;
subtypes: string[] | null;
denomination: string | null;
lgbtq_inclusive: boolean | null;
inclusivity_signals: string[] | null;
rating: number | null;
reviews_count: number | null;
photos_count: number | null;
photo_url: string | null;
logo_url: string | null;
description: string | null;
about: Record<string, unknown> | null; // ~20 JSONB fields
working_hours: Record<string, string[]> | null;
google_maps_url: string | null;
is_premium: boolean;
created_at: string;
website_scraped_at: string | null;
}
Performance Considerations
- Static generation: High-traffic church pages are ISR (Incremental Static Regeneration) with revalidation
- Image optimization:
photo_urlandlogo_urlare served through Next.js Image component with lazy loading - Map lazy loading: The Leaflet map only loads when the user scrolls to the map section (intersection observer)
- Nearby churches: Server-side query, not client-side -- avoids sending all church coordinates to the browser
See Also
- PewSearch Directory Overview -- parent document with full context
- Search System -- how users find churches before landing on this page
- Denomination Taxonomy -- denomination badge and filtering
- Data Quality -- why some fields are missing or inaccurate
- Pro Website Overview -- the enhanced hosted website for Premium subscribers