2026-05-11 23:56:42 -05:00
<!DOCTYPE html>
< html lang = "en" >
2026-05-12 01:04:17 -05:00
< head >
< meta charset = "UTF-8" / >
2026-05-17 22:01:27 -05:00
< link rel = "icon" type = "image/png" sizes = "32x32" href = "/favicon-32x32.png" / >
< link rel = "icon" type = "image/png" sizes = "16x16" href = "/favicon-16x16.png" / >
< link rel = "apple-touch-icon" sizes = "180x180" href = "/apple-touch-icon.png" / >
< link rel = "manifest" href = "/site.webmanifest" / >
2026-05-12 01:04:17 -05:00
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" / >
2026-05-18 14:02:12 -05:00
< meta name = "theme-color" content = "#0B1B3F" / >
2026-05-18 13:55:06 -05:00
< title > Queue North Technologies | Business Communications & IT Partner< / title >
< meta name = "description" content = "Queue North Technologies is a veteran-owned 8x8 Certified Partner providing business phone systems, UCaaS, contact center, IT support, and networking solutions. 25+ years of proven reliability." / >
<!-- Open Graph fallback for crawlers that don't execute JavaScript -->
< meta property = "og:title" content = "Queue North Technologies | Business Communications & IT Partner" / >
< meta property = "og:description" content = "Veteran-owned 8x8 Certified Partner. Business phone, UCaaS, contact center, IT support, and networking solutions. 25+ years of proven reliability." / >
< meta property = "og:url" content = "https://queuenorth.com" / >
< meta property = "og:type" content = "website" / >
< meta property = "og:site_name" content = "Queue North Technologies" / >
< meta property = "og:locale" content = "en_US" / >
< meta property = "og:image" content = "https://queuenorth.com/assets/og-image.png" / >
< meta property = "og:image:secure_url" content = "https://queuenorth.com/assets/og-image.png" / >
< meta property = "og:image:type" content = "image/png" / >
< meta property = "og:image:width" content = "1200" / >
< meta property = "og:image:height" content = "630" / >
ui: no em dashes in anything a visitor or a crawler reads
Null's direction of 2026-09-10. Punctuation only: not one word changed.
Nine of them, and the share-image alt text was on every page twice:
src/components/SEO.jsx:4 the default og:image and twitter:image alt
index.html:25,:31 the same string, as template fallbacks
src/pages/Home.jsx:257,:290
src/pages/Services.jsx:99
src/pages/Contact.jsx:183
src/data/services.js:120 the wireless card on Home and the index
public/site.webmanifest:4 linked from every page
Thirteen more sit in code comments and reach no page, so they stay. The hours
range on /contact uses en dashes, which are a different character and are not
what was asked about.
This lands before the guards that reject an em dash in content or in built
HTML, because until now every built page carried one and those guards would
have gone red on arrival.
Verified against the build, not the source: no U+2014 in any file under dist/,
including the HTML, the webmanifest, the sitemap and the JS bundle.
Closes #222.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 04:26:21 -05:00
< meta property = "og:image:alt" content = "Queue North Technologies: Business Communications & IT Partner" / >
2026-05-18 13:55:06 -05:00
<!-- Twitter / X Card fallback -->
< meta name = "twitter:card" content = "summary_large_image" / >
< meta name = "twitter:title" content = "Queue North Technologies | Business Communications & IT Partner" / >
< meta name = "twitter:description" content = "Veteran-owned 8x8 Certified Partner. Business phone, UCaaS, contact center, IT support, and networking solutions." / >
< meta name = "twitter:image" content = "https://queuenorth.com/assets/og-image.png" / >
ui: no em dashes in anything a visitor or a crawler reads
Null's direction of 2026-09-10. Punctuation only: not one word changed.
Nine of them, and the share-image alt text was on every page twice:
src/components/SEO.jsx:4 the default og:image and twitter:image alt
index.html:25,:31 the same string, as template fallbacks
src/pages/Home.jsx:257,:290
src/pages/Services.jsx:99
src/pages/Contact.jsx:183
src/data/services.js:120 the wireless card on Home and the index
public/site.webmanifest:4 linked from every page
Thirteen more sit in code comments and reach no page, so they stay. The hours
range on /contact uses en dashes, which are a different character and are not
what was asked about.
This lands before the guards that reject an em dash in content or in built
HTML, because until now every built page carried one and those guards would
have gone red on arrival.
Verified against the build, not the source: no U+2014 in any file under dist/,
including the HTML, the webmanifest, the sitemap and the JS bundle.
Closes #222.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 04:26:21 -05:00
< meta name = "twitter:image:alt" content = "Queue North Technologies: Business Communications & IT Partner" / >
feat(seo): publish privacy policy, remove street address, prerender all routes (batch 0.9.3)
Client directive (Levi Halford, 2026-08-01) ahead of Google/Meta lead forms.
Privacy policy:
- Publish approved policy verbatim at /privacy-policy (src/data/privacyPolicy.js
is the single source of truth; 292/292 source lines verified present)
- Privacy Policy link in the footer of every page
- Effective/Last Updated 2026-07-31, privacy@queuenorth.com as mailto
Remove St. Petersburg street address from every surface named in the brief:
footer, contact page, schema markup, SEO metadata, Google Maps links. Collapse
ProfessionalService + Organization schema into a single Organization with
areaServed: United States; drop geo coordinates, priceRange, openingHours.
Add the approved US-coverage sentence to About. No replacement address.
Crawler visibility (the site previously served 0 bytes of body HTML without JS):
- Prerender all 19 routes at build time via src/entry-server.jsx + scripts/prerender.js
- Hoist title/meta/canonical/JSON-LD into <head>; renderToString does not do this
and react-helmet-async's context is empty under React 19
- Serve prerendered HTML; return a real 404 for unknown paths instead of 200
- Hydrate instead of discarding the prerendered markup
SEO/perf:
- Titles <=60 and descriptions <=160 chars across all pages
- Add BreadcrumbList to interior pages, WebSite to home
- Generate sitemap.xml from the route list with git-derived lastmod
- 301 duplicate URL forms (trailing slash, //, /index.html), preserving query
- Immutable caching for content-hashed assets; no-cache for HTML
- Split the 522 KB bundle into app/react-vendor/router/icons
- loading/decoding/fetchpriority + per-route hero preload; drop unused asset
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 01:45:52 -05:00
<!-- The prerenderer injects a per - route <link rel="preload"> for that page's
hero image here (scripts/prerender.js). -->
2026-05-12 01:04:17 -05:00
< link rel = "preconnect" href = "https://fonts.googleapis.com" >
< link rel = "preconnect" href = "https://fonts.gstatic.com" crossorigin >
feat(seo): publish privacy policy, remove street address, prerender all routes (batch 0.9.3)
Client directive (Levi Halford, 2026-08-01) ahead of Google/Meta lead forms.
Privacy policy:
- Publish approved policy verbatim at /privacy-policy (src/data/privacyPolicy.js
is the single source of truth; 292/292 source lines verified present)
- Privacy Policy link in the footer of every page
- Effective/Last Updated 2026-07-31, privacy@queuenorth.com as mailto
Remove St. Petersburg street address from every surface named in the brief:
footer, contact page, schema markup, SEO metadata, Google Maps links. Collapse
ProfessionalService + Organization schema into a single Organization with
areaServed: United States; drop geo coordinates, priceRange, openingHours.
Add the approved US-coverage sentence to About. No replacement address.
Crawler visibility (the site previously served 0 bytes of body HTML without JS):
- Prerender all 19 routes at build time via src/entry-server.jsx + scripts/prerender.js
- Hoist title/meta/canonical/JSON-LD into <head>; renderToString does not do this
and react-helmet-async's context is empty under React 19
- Serve prerendered HTML; return a real 404 for unknown paths instead of 200
- Hydrate instead of discarding the prerendered markup
SEO/perf:
- Titles <=60 and descriptions <=160 chars across all pages
- Add BreadcrumbList to interior pages, WebSite to home
- Generate sitemap.xml from the route list with git-derived lastmod
- 301 duplicate URL forms (trailing slash, //, /index.html), preserving query
- Immutable caching for content-hashed assets; no-cache for HTML
- Split the 522 KB bundle into app/react-vendor/router/icons
- loading/decoding/fetchpriority + per-route hero preload; drop unused asset
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 01:45:52 -05:00
<!-- Kept as a plain stylesheet link: the usual media="print"/onload swap needs
an inline event handler, which this site's CSP (script-src 'self') blocks.
display=swap already prevents invisible text while the font loads. -->
2026-05-12 01:04:17 -05:00
< link href = "https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel = "stylesheet" >
< / head >
< body >
< div id = "root" > < / div >
< script type = "module" src = "/src/main.jsx" > < / script >
< / body >
< / html >