Implementing Comprehensive SEO for Property Listings in PropertyWebBuilder
PropertyWebBuilder provides a full-stack SEO subsystem covering metadata generation, JSON-LD structured data, field validation, and an audit dashboard, enabling search-engine-optimized real estate listings without external gems.
PropertyWebBuilder (PWB) is a Ruby on Rails engine that implements comprehensive SEO for property listings through a cohesive architecture of models, helpers, and admin interfaces. The system stores SEO attributes directly on content entities, validates them against search engine constraints, and renders complete meta tag suites including Open Graph, Twitter Cards, and schema.org JSON-LD.
How SEO Data Is Stored in PropertyWebBuilder
The platform persists SEO fields at three levels: site-wide defaults, CMS pages, and individual properties.
Website-level defaults live in app/models/pwb/website.rb (line 28), which exposes default_seo_title and default_meta_description columns on the pwb_websites table. These values serve as fallbacks when specific pages or properties lack custom SEO content.
CMS pages defined in app/models/pwb/page.rb (line 52) store seo_title, meta_description, and meta_keywords directly on the pwb_pages table. Administrators edit these through the Site Admin interface at /site_admin/website/settings/seo.
Property listings utilize a hybrid approach. The base Pwb::Prop model in app/models/pwb/prop.rb (line 71) provides a seo_title column for direct storage. Additionally, specific listing types—Pwb::SaleListing (line 64), Pwb::RentalListing (line 67), and Pwb::SppListing—leverage the Mobility gem to store translated SEO content in seo_title and meta_description fields per locale.
Validating SEO Content with SeoValidatable
To enforce search engine best practices, PWB includes the SeoValidatable concern in app/models/concerns/seo_validatable.rb. This module validates field lengths and presence for each locale, ensuring translated SEO titles and descriptions meet Google's character limit recommendations.
# app/models/concerns/seo_validatable.rb
module SeoValidatable
extend ActiveSupport::Concern
included do
validate :validate_seo_field_lengths
end
def validate_seo_title_for_locale(locale)
field_name = "seo_title_#{locale}"
# Enforces length constraints per translation
end
end
The concern is mixed into any model declaring translatable SEO attributes, automatically running checks at save time (lines 17–30).
Rendering Meta Tags and Structured Data
All meta tag generation logic resides in app/helpers/seo_helper.rb. This helper provides a fluent API for setting and rendering SEO data across the application.
Core Helper Methods
set_seo(lines 14–17): Stores an options hash for the current request in@seo_data, accepting:title,:description,:canonical_url,:og_type, and:image.seo_title(lines 24–40): Resolves the page title using a precedence chain: explicit SEO title → entity title → website default.seo_meta_tags(lines 107–156): Assembles the complete<head>output including favicons, verification meta tags, Open Graph (og:*) properties, Twitter Card tags, robots directives, andalternatehreflang links for multilingual sites.generate_alternate_urls(lines 74–93): Generates hreflang URLs for each available locale to signal language variants to search engines.
JSON-LD Structured Data Generators
The helper includes generators for schema.org markup:
property_json_ld(lines 59–145): Injects RealEstateListing structured data for rich snippets in Google search results.organization_json_ld: Provides Organization schema for the real estate agency.breadcrumb_json_ld: Enables breadcrumb rich results.
Integrating SEO in Controllers
Controllers invoke helper methods to populate SEO data before rendering views.
For CMS pages, use set_page_seo (lines 98–114):
def show
@page = Pwb::Page.find_by!(slug: params[:slug])
set_page_seo(@page)
render :show
end
For search and listing pages, set_listing_page_seo (lines 19–47) constructs dynamic titles based on operation type, location, and pagination:
def index
set_listing_page_seo(
operation: params[:type],
location: params[:city],
page: params[:page]
)
end
For individual properties, controllers manually configure SEO:
# app/controllers/properties_controller.rb
class PropertiesController < ApplicationController
include SeoHelper
def show
@property = Pwb::Prop.find(params[:id])
canonical = property_url(@property)
set_seo(
title: @property.seo_title.presence || @property.title,
description: @property.meta_description.presence,
canonical_url: canonical,
og_type: 'product',
image: @property.photos.first
)
end
end
Monitoring SEO Health with the Audit Dashboard
The SiteAdmin::SeoAuditController (app/controllers/site_admin/seo_audit_controller.rb) provides a comprehensive health overview at /site_admin/seo_audit (route defined in config/routes.rb, line 286).
The controller computes:
- Property statistics: Percentage of listings with populated SEO titles and descriptions (lines 32–55).
- Page statistics: Coverage metrics for CMS pages (lines 66–78).
- Image statistics: Count of property photos containing
alttext (lines 87–107). - Overall score: A weighted aggregate score and letter grade (lines 119–133).
Instance variables @property_stats, @page_stats, @image_stats, and @overall_score expose this data to the admin view, guiding content editors toward incomplete listings.
Complete Implementation Workflow
1. Configure SEO in the Layout
Render the complete meta tag suite in your application layout:
<!-- app/views/layouts/application.html.erb -->
<head>
<meta charset="utf-8">
<%= seo_meta_tags %>
<%= property_json_ld(@property) if defined?(@property) %>
<%= organization_json_ld %>
</head>
2. Admin Form for SEO Fields
Allow editors to customize SEO content via standard Rails forms:
<!-- app/views/site_admin/pages/_form.html.erb -->
<div class="field">
<%= f.label :seo_title, "SEO Title (optional)" %>
<%= f.text_field :seo_title, class: "form-control", placeholder: "e.g. Luxury Villa in Barcelona" %>
</div>
<div class="field">
<%= f.label :meta_description, "Meta Description (optional)" %>
<%= f.text_area :meta_description, rows: 3, class: "form-control" %>
</div>
3. Programmatic SEO Health Checks
Create Rake tasks to audit SEO coverage across websites:
# lib/tasks/seo.rake
namespace :seo do
desc "Print SEO health summary for all websites"
task summary: :environment do
Pwb::Website.find_each do |website|
puts "Website: #{website.subdomain}"
puts " Default title: #{website.default_seo_title}"
puts " Pages with SEO title: #{website.pages.where.not(seo_title: nil).count}"
puts " Props with SEO title: #{website.props.where.not(seo_title: nil).count}"
end
end
end
This leverages the same metrics used by the audit dashboard controller.
Summary
- PropertyWebBuilder stores SEO data on
Pwb::Website,Pwb::Page, andPwb::Propmodels, with listing types supporting translated fields via Mobility. - The SeoValidatable concern enforces length constraints and locale-specific validation rules.
- SeoHelper centralizes meta tag generation, canonical URLs, hreflang links, and JSON-LD structured data in
app/helpers/seo_helper.rb. - Controllers use
set_seo,set_page_seo, orset_listing_page_seoto populate per-request SEO data. - The SEO Audit Dashboard at
/site_admin/seo_audittracks completion rates for titles, descriptions, and image alt text across all listings.
Frequently Asked Questions
How do I add custom SEO titles to property listings in PropertyWebBuilder?
Set the seo_title attribute on the Pwb::Prop model or its translated variants via the admin interface. The system falls back to the property's standard title if the SEO field is blank. According to app/models/pwb/prop.rb (line 71), this column stores the override value used by SeoHelper#seo_title.
Where does PropertyWebBuilder store site-wide default SEO values?
Site defaults reside in the pwb_websites table, accessible through the Pwb::Website model in app/models/pwb/website.rb (line 28). The default_seo_title and default_meta_description columns provide fallback content for pages and properties that lack specific SEO data, configurable via /site_admin/website/settings/seo.
Does PropertyWebBuilder support structured data for Google rich snippets?
Yes. The SeoHelper module in app/helpers/seo_helper.rb includes property_json_ld (lines 59–145) which generates schema.org RealEstateListing JSON-LD. When rendered in the layout alongside seo_meta_tags, this enables rich results including property images, prices, and locations in search engine results.
How can I validate SEO field lengths across multiple languages?
Include the SeoValidatable concern in models with translated SEO attributes. Defined in app/models/concerns/seo_validatable.rb (lines 17–30), this module validates character limits for each locale separately, ensuring that seo_title_en, seo_title_es, and other translations comply with search engine display limits before saving.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →