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:
Concept Agent
2026-05-31 14:09:19 +02:00
parent 81feccdc1a
commit fb32f28bd4
7 changed files with 108 additions and 56 deletions
+8 -28
View File
@@ -1,8 +1,7 @@
(function () {
var POPUP_KEY = 'flooritPromo2026';
var TOPBAR_KEY = 'flooritTopbar2026';
var DELAY_MS = 5000;
var EXPIRY_MS = 7 * 24 * 60 * 60 * 1000;
var POPUP_KEY = 'flooritPromo2026';
var DELAY_MS = 5000;
var EXPIRY_MS = 7 * 24 * 60 * 60 * 1000;
function isStored(key) {
try {
@@ -15,32 +14,14 @@
try { localStorage.setItem(key, String(Date.now() + EXPIRY_MS)); } catch (e) {}
}
/* --- Topbar -------------------------------------------- */
function showTopbar() {
var bar = document.getElementById('promo-topbar');
/* --- 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
View File
@@ -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
View File
@@ -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>
+3 -1
View File
@@ -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">&times;</button>
</div>
<header id="site-header" class="site-header">
<div class="container">
+19
View File
@@ -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';
?>
+16
View File
@@ -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) {
+25
View File
@@ -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';
?>