Skip to main content

Knowledge > Products > Pro Website > Vanity URLs

Pro Website Vanity URLs

What Vanity URLs Do

Vanity URLs give Pro Website churches a custom subdomain on pewsearch.com. Instead of accessing their church page at pewsearch.com/churches/grace-community-church-springfield-il, the church can share grace-community.pewsearch.com -- a clean, memorable URL they can put on business cards, bulletins, and social media.

The vanity URL serves the exact same Pro Website content as the standard church detail page. It is a routing alias, not a separate page.

Slug Validation

Rules

The vanity slug must pass this validation before it is saved:

Regex: /^[a-z0-9][a-z0-9-]{1,28}[a-z0-9]$/

Rules:
- Minimum length: 3 characters
- Maximum length: 30 characters
- Must start with a lowercase letter or digit
- Must end with a lowercase letter or digit
- May contain hyphens in the middle
- No uppercase letters (auto-lowercased in UI)
- No spaces
- No underscores
- No special characters (! @ # $ % etc.)
- No consecutive hyphens (--) — not enforced by regex but rejected by UI validation

Valid Examples

SlugURL
grace-communitygrace-community.pewsearch.com
first-baptistfirst-baptist.pewsearch.com
st-josephst-joseph.pewsearch.com
thebridgethebridge.pewsearch.com
c3c3.pewsearch.com

Invalid Examples

Attempted SlugReason
Grace-CommunityUppercase letters
-graceStarts with hyphen
grace-Ends with hyphen
grToo short (min 3)
this-is-a-very-long-slug-name-tooToo long (max 30)
grace communityContains space
grace_communityContains underscore

Uniqueness

Vanity slugs must be unique across all premium_churches records. The admin UI performs a server-side uniqueness check before saving. If the slug is taken, the UI shows an error message suggesting alternatives.

Reserved Subdomains

The following subdomains are reserved and cannot be used as vanity slugs:

ReservedReason
wwwDefault web subdomain
apiAPI routing
adminAdmin dashboard
appFuture application subdomain

These are excluded in middleware.ts before any vanity slug lookup occurs.

Routing Architecture

Three Access Paths

A Pro Website page can be accessed via three different URL patterns:

PathExampleHow It Works
Subdomaingrace-community.pewsearch.comMiddleware rewrites to /s/grace-community
Direct slug routepewsearch.com/s/grace-communityDirect page render
Catch-all vanitypewsearch.com/grace-communityCatch-all route, same render

All three paths ultimately render the same page using the same data and template.

Middleware: Subdomain Detection

The middleware (middleware.ts) runs on every request and handles subdomain-to-path rewriting:

Middleware flow:

1. Extract hostname from request
2. Parse subdomain:
hostname = "grace-community.pewsearch.com"
subdomain = "grace-community"

3. Skip conditions (do NOT rewrite):
- IF hostname is localhost → skip (local dev)
- IF subdomain is in RESERVED_SUBDOMAINS (www, api, admin, app) → skip
- IF no subdomain (bare pewsearch.com) → skip

4. IF valid subdomain detected:
- Set header: x-is-subdomain = "true"
- Rewrite URL: /s/{subdomain}
- The layout reads x-is-subdomain to hide site chrome (PewSearch nav, footer)

5. Continue to Next.js page rendering

The x-is-subdomain Header

When a request comes through a subdomain, the middleware sets x-is-subdomain: true in the request headers. The root layout component reads this header and conditionally hides the standard PewSearch site chrome (main navigation bar, site-wide footer, directory search). This makes the Pro Website page feel like a standalone church website rather than a page within the PewSearch directory.

IF x-is-subdomain header is "true":
Hide: PewSearch main nav
Hide: PewSearch site footer
Hide: Directory search bar
Hide: "Back to directory" breadcrumb
Show: Only the UnifiedTemplate content (with its own StickyNav and Footer)
ELSE:
Show: Full PewSearch chrome wrapping the church page

Page Component: /s/[slug]

Located at pewsearch/web/src/app/(website)/s/[slug]/page.tsx, this is the primary rendering page for vanity URLs.

Page data loading:

1. Receive slug parameter from URL
2. Query premium_churches WHERE vanity_slug = slug
3. IF not found: fallback query churches WHERE slug = slug
4. IF still not found: return 404
5. Load full church data (join churches + premium_churches)
6. Compute denomination style (getStyleByDenomination)
7. Compute template labels
8. Render UnifiedTemplate with all data

The fallback to the church's standard slug means that /s/grace-community-church-springfield-il would also work even if the vanity slug is just grace-community. The vanity slug takes priority.

Catch-All Route: /[vanity]

Located at pewsearch/web/src/app/[vanity]/page.tsx, this is a catch-all route that also serves vanity URLs. It exists to handle the case where someone navigates to pewsearch.com/grace-community directly (without the /s/ prefix).

Catch-all behavior:

1. Receive vanity parameter from URL
2. Check if vanity matches any known route (directory, admin, etc.)
IF yes: let Next.js handle normally (not a vanity URL)
3. Query premium_churches WHERE vanity_slug = vanity
4. IF found: render Pro Website page (same as /s/[slug])
5. IF not found: return 404

This route has lower priority than explicit routes in the Next.js routing hierarchy. It only catches requests that do not match any other defined route.

ISR and Caching

Pro Website pages rendered via vanity URLs use Incremental Static Regeneration (ISR) with a 1-hour revalidation period:

export const revalidate = 3600; // 1 hour

This means:

  • The first visit after a content change may show stale data (up to 1 hour)
  • Subsequent visits within the revalidation window get the cached version
  • After 1 hour, the next visit triggers a background regeneration
  • The stale page is served while regeneration happens (stale-while-revalidate pattern)

Cache Implications

ScenarioWhat Happens
Church updates staff in adminChange visible within 1 hour
Church sets new vanity slugNew slug active within 1 hour, old slug may serve cached page briefly
Church changes hero videoNew video visible within 1 hour
New Pro Website createdFirst visit generates the page, subsequent visits are cached

There is no on-demand revalidation trigger from the admin dashboard. This is a known trade-off: simplicity over immediacy. Churches are told "changes may take up to an hour to appear."

SEO

Structured Data (JSON-LD)

Pro Website pages include structured data for search engines:

{
"@context": "https://schema.org",
"@type": "Church",
"name": "Grace Community Church",
"address": {
"@type": "PostalAddress",
"streetAddress": "123 Main St",
"addressLocality": "Springfield",
"addressRegion": "IL",
"postalCode": "62701"
},
"telephone": "+1-555-123-4567",
"openingHours": [
"Su 09:00-10:30",
"Su 10:30-12:00",
"We 19:00-20:30"
],
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.5",
"reviewCount": "23"
}
}

For pages accessed within the PewSearch chrome (not subdomain), breadcrumb structured data is included:

{
"@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": "Grace Community Church"}
]
}

Meta Tags

Each Pro Website page generates dynamic meta tags:

<title>Grace Community Church | Springfield, IL</title>
<meta name="description" content="Welcome to Grace Community Church in Springfield, IL. Sunday worship at 9:00 AM and 10:30 AM. [First 160 chars of description]" />
<meta property="og:title" content="Grace Community Church" />
<meta property="og:description" content="..." />
<meta property="og:image" content="[poster image from hero video]" />
<link rel="canonical" href="https://grace-community.pewsearch.com" />

Canonical URL: When a vanity slug exists, the canonical URL points to the subdomain version. This tells search engines to index the subdomain URL as the primary version, avoiding duplicate content issues between the three access paths.

Local Development

Testing Subdomains Locally

Subdomain routing does not work on localhost because:

  • localhost has no real DNS
  • Browser security policies treat subdomains differently on localhost
  • The middleware explicitly skips subdomain detection for localhost

How to test vanity URLs locally:

Use the direct path instead of the subdomain:

# Instead of: grace-community.localhost:3000
# Use: localhost:3000/s/grace-community

This bypasses the middleware's subdomain detection but renders the same page component. The x-is-subdomain header will not be set, so PewSearch chrome will be visible, but the content is identical.

Data Model

ColumnTypeConstraintsPurpose
vanity_slugtextnullable, uniqueThe subdomain slug

The vanity_slug column is:

  • Nullable: Not all Pro Website churches choose to set a vanity URL
  • Unique: No two churches can have the same vanity slug
  • Indexed: For fast lookup in the middleware rewrite path

Edge Cases

ScenarioBehavior
Church deletes vanity slugSubdomain stops working within ISR revalidation window. /s/ and /[vanity] routes return 404 for old slug.
Church changes vanity slugOld subdomain stops working, new one starts. Both subject to ISR cache timing.
Two churches want same slugSecond church gets uniqueness error. First-come, first-served.
Vanity slug conflicts with existing routeCatch-all has lowest priority; explicit routes (e.g., /directory, /admin) always win. Not a real conflict.
Church has no Pro Website planvanity_slug can exist in the DB but the page would render a basic listing, not the full UnifiedTemplate. In practice, vanity slugs are only set by Pro Website customers.

See Also