SEO + UX: schema.org JSON-LD, sitemap fix, persistent topbar, POST-only rate limit
- Add schema.org JSON-LD to page, service, and location templates (LocalBusiness / Service / FAQPage) - Inject schema_json via _header.php <script type="application/ld+json"> block - Fix sitemap.xml: replace 10 stale .html refs with trailing-slash URLs, add /blog/, update lastmod - nginx: rate-limit /contact/ POST-only via map $request_method (GET no longer hits 429) - Topbar: remove dismiss button, always visible for session; popup close no longer hides topbar Verified: all 17 routes 200, JSON-LD present on home/service/location/contact, sitemap 17 URLs no .html Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
(function () {
|
||||
var POPUP_KEY = 'flooritPromo2026';
|
||||
var TOPBAR_KEY = 'flooritTopbar2026';
|
||||
var DELAY_MS = 5000;
|
||||
var EXPIRY_MS = 7 * 24 * 60 * 60 * 1000;
|
||||
|
||||
@@ -15,32 +14,14 @@
|
||||
try { localStorage.setItem(key, String(Date.now() + EXPIRY_MS)); } catch (e) {}
|
||||
}
|
||||
|
||||
/* --- Topbar -------------------------------------------- */
|
||||
function showTopbar() {
|
||||
/* --- Topbar (always visible, no dismiss) --------------- */
|
||||
function initTopbar() {
|
||||
var bar = document.getElementById('promo-topbar');
|
||||
var offerBtn = document.getElementById('promo-topbar-btn');
|
||||
if (!bar) return;
|
||||
bar.classList.add('visible');
|
||||
document.body.classList.add('has-topbar');
|
||||
}
|
||||
|
||||
function hideTopbar() {
|
||||
var bar = document.getElementById('promo-topbar');
|
||||
if (!bar) return;
|
||||
bar.classList.remove('visible');
|
||||
document.body.classList.remove('has-topbar');
|
||||
store(TOPBAR_KEY);
|
||||
}
|
||||
|
||||
function initTopbar() {
|
||||
if (isStored(TOPBAR_KEY) || isStored(POPUP_KEY)) return;
|
||||
showTopbar();
|
||||
var closeBtn = document.getElementById('promo-topbar-close');
|
||||
var offerBtn = document.getElementById('promo-topbar-btn');
|
||||
if (closeBtn) closeBtn.addEventListener('click', hideTopbar);
|
||||
if (offerBtn) offerBtn.addEventListener('click', function () {
|
||||
hideTopbar();
|
||||
openPopup();
|
||||
});
|
||||
if (offerBtn) offerBtn.addEventListener('click', openPopup);
|
||||
}
|
||||
|
||||
/* --- Popup --------------------------------------------- */
|
||||
@@ -60,7 +41,6 @@
|
||||
setTimeout(function () { overlay.style.display = 'none'; }, 350);
|
||||
}
|
||||
store(POPUP_KEY);
|
||||
hideTopbar();
|
||||
}
|
||||
|
||||
function initPopup() {
|
||||
|
||||
+5
-1
@@ -26,7 +26,11 @@ http {
|
||||
gzip_types text/html text/css application/javascript image/svg+xml;
|
||||
gzip_min_length 1024;
|
||||
|
||||
limit_req_zone $binary_remote_addr zone=contact_limit:10m rate=5r/m;
|
||||
map $request_method $contact_rl_key {
|
||||
POST $binary_remote_addr;
|
||||
default "";
|
||||
}
|
||||
limit_req_zone $contact_rl_key zone=contact_limit:10m rate=5r/m;
|
||||
|
||||
server {
|
||||
listen 80 default_server;
|
||||
|
||||
+32
-26
@@ -2,97 +2,103 @@
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://floorithardwoodfloors.com/</loc>
|
||||
<lastmod>2026-05-27</lastmod>
|
||||
<lastmod>2026-05-31</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://floorithardwoodfloors.com/about/</loc>
|
||||
<lastmod>2026-05-27</lastmod>
|
||||
<lastmod>2026-05-31</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://floorithardwoodfloors.com/contact/</loc>
|
||||
<lastmod>2026-05-27</lastmod>
|
||||
<lastmod>2026-05-31</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://floorithardwoodfloors.com/reviews/</loc>
|
||||
<lastmod>2026-05-27</lastmod>
|
||||
<lastmod>2026-05-31</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://floorithardwoodfloors.com/blog/</loc>
|
||||
<lastmod>2026-05-31</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://floorithardwoodfloors.com/services/</loc>
|
||||
<lastmod>2026-05-27</lastmod>
|
||||
<lastmod>2026-05-31</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://floorithardwoodfloors.com/services/floor-refinishing.html</loc>
|
||||
<lastmod>2026-05-27</lastmod>
|
||||
<loc>https://floorithardwoodfloors.com/services/floor-installation/</loc>
|
||||
<lastmod>2026-05-31</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.85</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://floorithardwoodfloors.com/services/floor-installation.html</loc>
|
||||
<lastmod>2026-05-27</lastmod>
|
||||
<loc>https://floorithardwoodfloors.com/services/floor-refinishing/</loc>
|
||||
<lastmod>2026-05-31</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.85</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://floorithardwoodfloors.com/services/floor-restoration.html</loc>
|
||||
<lastmod>2026-05-27</lastmod>
|
||||
<loc>https://floorithardwoodfloors.com/services/floor-restoration/</loc>
|
||||
<lastmod>2026-05-31</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.85</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://floorithardwoodfloors.com/services/floor-sanding.html</loc>
|
||||
<lastmod>2026-05-27</lastmod>
|
||||
<loc>https://floorithardwoodfloors.com/services/floor-sanding/</loc>
|
||||
<lastmod>2026-05-31</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.85</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://floorithardwoodfloors.com/locations/</loc>
|
||||
<lastmod>2026-05-27</lastmod>
|
||||
<lastmod>2026-05-31</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://floorithardwoodfloors.com/locations/buffalo.html</loc>
|
||||
<lastmod>2026-05-27</lastmod>
|
||||
<loc>https://floorithardwoodfloors.com/locations/buffalo/</loc>
|
||||
<lastmod>2026-05-31</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.85</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://floorithardwoodfloors.com/locations/amherst.html</loc>
|
||||
<lastmod>2026-05-27</lastmod>
|
||||
<loc>https://floorithardwoodfloors.com/locations/amherst/</loc>
|
||||
<lastmod>2026-05-31</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://floorithardwoodfloors.com/locations/williamsville.html</loc>
|
||||
<lastmod>2026-05-27</lastmod>
|
||||
<loc>https://floorithardwoodfloors.com/locations/williamsville/</loc>
|
||||
<lastmod>2026-05-31</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://floorithardwoodfloors.com/locations/east-amherst.html</loc>
|
||||
<lastmod>2026-05-27</lastmod>
|
||||
<loc>https://floorithardwoodfloors.com/locations/clarence/</loc>
|
||||
<lastmod>2026-05-31</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://floorithardwoodfloors.com/locations/clarence.html</loc>
|
||||
<lastmod>2026-05-27</lastmod>
|
||||
<loc>https://floorithardwoodfloors.com/locations/east-amherst/</loc>
|
||||
<lastmod>2026-05-31</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://floorithardwoodfloors.com/locations/lancaster.html</loc>
|
||||
<lastmod>2026-05-27</lastmod>
|
||||
<loc>https://floorithardwoodfloors.com/locations/lancaster/</loc>
|
||||
<lastmod>2026-05-31</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
|
||||
@@ -10,6 +10,7 @@ while ($row = $nav_result->fetchArray(SQLITE3_ASSOC)) {
|
||||
}
|
||||
$db->close();
|
||||
$canonical = $canonical ?? '';
|
||||
$schema_json = $schema_json ?? '';
|
||||
$page_title = htmlspecialchars($page_title ?? 'Floor It Hardwood Floors', ENT_QUOTES);
|
||||
$page_meta = htmlspecialchars($page_meta ?? '', ENT_QUOTES);
|
||||
?><!DOCTYPE html>
|
||||
@@ -21,6 +22,8 @@ $page_meta = htmlspecialchars($page_meta ?? '', ENT_QUOTES);
|
||||
<meta name="description" content="<?= $page_meta ?>">
|
||||
<?php if ($canonical): ?><link rel="canonical" href="<?= htmlspecialchars($canonical, ENT_QUOTES) ?>">
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($schema_json)): ?><script type="application/ld+json"><?= $schema_json ?></script>
|
||||
<?php endif; ?>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
@@ -32,7 +35,6 @@ $page_meta = htmlspecialchars($page_meta ?? '', ENT_QUOTES);
|
||||
<div id="promo-topbar" role="complementary" aria-label="Summer promotion">
|
||||
<span id="promo-topbar-text">Summer Refinishing Savings: Save up to 15% through June 30, 2026</span>
|
||||
<button id="promo-topbar-btn" type="button">Get Offer</button>
|
||||
<button id="promo-topbar-close" type="button" aria-label="Dismiss promotion">×</button>
|
||||
</div>
|
||||
<header id="site-header" class="site-header">
|
||||
<div class="container">
|
||||
|
||||
@@ -22,6 +22,25 @@ $body = json_decode($loc['body_json'] ?? '{}', true) ?? [];
|
||||
$faqs = json_decode($loc['faqs_json'] ?? '[]', true) ?? [];
|
||||
$city = $loc['city'];
|
||||
|
||||
$schema_json = json_encode([
|
||||
'@context' => 'https://schema.org',
|
||||
'@type' => 'HomeAndConstructionBusiness',
|
||||
'name' => 'Floor It Hardwood Floors',
|
||||
'url' => 'https://floorithardwoodfloors.com',
|
||||
'telephone'=> '+17166021429',
|
||||
'address' => [
|
||||
'@type' => 'PostalAddress',
|
||||
'addressLocality' => $loc['city'],
|
||||
'addressRegion' => 'NY',
|
||||
'addressCountry' => 'US',
|
||||
],
|
||||
'areaServed' => [
|
||||
'@type' => 'City',
|
||||
'name' => $loc['city'],
|
||||
],
|
||||
'description' => $loc['meta_description'],
|
||||
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
|
||||
require __DIR__ . '/../components/_header.php';
|
||||
?>
|
||||
|
||||
|
||||
@@ -20,6 +20,22 @@ $page_title = $page['title'];
|
||||
$page_meta = $page['meta_description'];
|
||||
$sections = json_decode($page['sections_json'], true) ?? [];
|
||||
|
||||
$schema_json = json_encode([
|
||||
'@context' => 'https://schema.org',
|
||||
'@type' => 'HomeAndConstructionBusiness',
|
||||
'name' => 'Floor It Hardwood Floors',
|
||||
'url' => 'https://floorithardwoodfloors.com',
|
||||
'telephone'=> '+17166021429',
|
||||
'address' => [
|
||||
'@type' => 'PostalAddress',
|
||||
'addressLocality' => 'Buffalo',
|
||||
'addressRegion' => 'NY',
|
||||
'addressCountry' => 'US',
|
||||
],
|
||||
'areaServed' => ['Buffalo','Amherst','Williamsville','Clarence','East Amherst','Lancaster'],
|
||||
'description' => $page['meta_description'],
|
||||
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
|
||||
require __DIR__ . '/../components/_header.php';
|
||||
|
||||
foreach ($sections as $s) {
|
||||
|
||||
@@ -21,6 +21,31 @@ $page_meta = $svc['meta_description'];
|
||||
$body = json_decode($svc['body_json'] ?? '{}', true) ?? [];
|
||||
$faqs = json_decode($svc['faqs_json'] ?? '[]', true) ?? [];
|
||||
|
||||
$schema_arr = [
|
||||
'@context' => 'https://schema.org',
|
||||
'@type' => 'Service',
|
||||
'name' => $svc['title'],
|
||||
'description' => $svc['meta_description'],
|
||||
'provider' => [
|
||||
'@type' => 'HomeAndConstructionBusiness',
|
||||
'name' => 'Floor It Hardwood Floors',
|
||||
'url' => 'https://floorithardwoodfloors.com',
|
||||
'telephone'=> '+17166021429',
|
||||
],
|
||||
'areaServed' => 'Buffalo, NY',
|
||||
];
|
||||
if (!empty($faqs)) {
|
||||
$schema_arr['@graph'] = [[
|
||||
'@type' => 'FAQPage',
|
||||
'mainEntity' => array_map(fn($f) => [
|
||||
'@type' => 'Question',
|
||||
'name' => $f['q'],
|
||||
'acceptedAnswer' => ['@type' => 'Answer', 'text' => $f['a']],
|
||||
], $faqs),
|
||||
]];
|
||||
}
|
||||
$schema_json = json_encode($schema_arr, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
|
||||
require __DIR__ . '/../components/_header.php';
|
||||
?>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user