From 1b0d5adc3636e4df09289cdbca8240471cc58623 Mon Sep 17 00:00:00 2001 From: null Date: Sun, 17 May 2026 20:03:42 -0500 Subject: [PATCH] feat(seo): add react-helmet-async, per-page meta/OG tags, JSON-LD, sitemap, robots.txt, heading fixes (#71) - Added react-helmet-async + HelmetProvider to main.jsx - Per-page Helmet components on all 8 pages (title, description, OG tags) - JSON-LD structured data (Organization, LocalBusiness, Service) - Created public/sitemap.xml with all 17 routes - Created public/robots.txt - Fixed heading hierarchy (no h1->h3 skips) - Improved image alt text throughout - Fixed docs/zoho-setup.md env defaults clarification --- .gitignore | 1 + docs/zoho-setup.md | 21 +++++---- package-lock.json | 53 ++++++++++++++++++++-- package.json | 1 + public/robots.txt | 4 ++ public/sitemap.xml | 88 ++++++++++++++++++++++++++++++++++++ src/main.jsx | 11 +++-- src/pages/About.jsx | 13 +++++- src/pages/Contact.jsx | 11 +++++ src/pages/Home.jsx | 74 ++++++++++++++++++++++++++++-- src/pages/Industries.jsx | 17 +++++-- src/pages/IndustryDetail.jsx | 21 ++++++++- src/pages/ServiceDetail.jsx | 40 +++++++++++++++- src/pages/Services.jsx | 31 ++++++++++++- src/pages/Support.jsx | 11 +++++ 15 files changed, 368 insertions(+), 29 deletions(-) create mode 100644 public/robots.txt create mode 100644 public/sitemap.xml diff --git a/.gitignore b/.gitignore index f8d273d..10c189c 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ pnpm-debug.log* .vscode/ .idea/ .learnings/ +Levi.md diff --git a/docs/zoho-setup.md b/docs/zoho-setup.md index 4fb1fe0..a2df5cc 100644 --- a/docs/zoho-setup.md +++ b/docs/zoho-setup.md @@ -74,15 +74,19 @@ curl -X POST https://accounts.zoho.com/oauth/v2/token \ Add these to your `.env` file: ```env -ZOHO_ENABLED=true +# Zoho integration is OFF by default — set to true to enable +ZOHO_ENABLED=false ZOHO_API_DOMAIN=https://www.zohoapis.com ZOHO_ACCOUNTS_DOMAIN=https://accounts.zoho.com ZOHO_CLIENT_ID= ZOHO_CLIENT_SECRET= ZOHO_REFRESH_TOKEN= -ZOHO_CASES_ENABLED=true +# Cases forwarding is also OFF by default +ZOHO_CASES_ENABLED=false ``` +> **Note:** Both `ZOHO_ENABLED` and `ZOHO_CASES_ENABLED` default to `false`. Set them to `true` only after completing Steps 1–3 and verifying your credentials. + ### Datacenter Variants If your Zoho datacenter is **outside the US**, adjust the domains: @@ -165,12 +169,13 @@ Website Contact Form → SQLite (always saved) ## What Happens Next? -After configuration, your team (Ripley + Bishop) will: -1. Deploy environment variables to production -2. Run integration tests -3. Verify data flows to Zoho CRM -4. Update `PROJECT.md` with the integration status +After configuration: +1. Deploy the environment variables to production +2. Set `ZOHO_ENABLED=true` and `ZOHO_CASES_ENABLED=true` in production `.env` +3. Restart the application +4. Submit a test lead and support case to verify data flows to Zoho CRM +5. Check Zoho CRM Leads and Cases tabs to confirm both appear --- -**Questions?** Contact Ripley (Infrastructure) or Neo (Backend). +**Need help?** Contact your site administrator. diff --git a/package-lock.json b/package-lock.json index fc7e547..018ce2f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "queuenorth-website", - "version": "0.4.8", + "version": "0.6.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "queuenorth-website", - "version": "0.4.8", + "version": "0.6.6", "dependencies": { "@radix-ui/react-dialog": "^1.1.0", "@radix-ui/react-visually-hidden": "^1.2.4", @@ -19,6 +19,7 @@ "lucide-react": "^0.468.0", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-helmet-async": "^3.0.0", "react-router-dom": "^7.1.3", "sonner": "^1.7.0", "tailwindcss-animate": "^1.0.7", @@ -3132,6 +3133,15 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "license": "ISC" }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/ip-address": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", @@ -3230,7 +3240,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/jsesc": { @@ -3277,6 +3286,18 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -3913,6 +3934,26 @@ "react": "^19.2.6" } }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "license": "MIT" + }, + "node_modules/react-helmet-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-3.0.0.tgz", + "integrity": "sha512-nA3IEZfXiclgrz4KLxAhqJqIfFDuvzQwlKwpdmzZIuC1KNSghDEIXmyU0TKtbM+NafnkICcwx8CECFrZ/sL/1w==", + "license": "Apache-2.0", + "dependencies": { + "invariant": "^2.2.4", + "react-fast-compare": "^3.2.2", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -4305,6 +4346,12 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" + }, "node_modules/shell-quote": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", diff --git a/package.json b/package.json index 4db78fa..2a2b8b8 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "lucide-react": "^0.468.0", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-helmet-async": "^3.0.0", "react-router-dom": "^7.1.3", "sonner": "^1.7.0", "tailwindcss-animate": "^1.0.7", diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..e979752 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://queuenorth.com/sitemap.xml \ No newline at end of file diff --git a/public/sitemap.xml b/public/sitemap.xml new file mode 100644 index 0000000..38e480d --- /dev/null +++ b/public/sitemap.xml @@ -0,0 +1,88 @@ + + + + https://queuenorth.com + weekly + 1.0 + + + https://queuenorth.com/about + monthly + 0.8 + + + https://queuenorth.com/services + monthly + 0.9 + + + https://queuenorth.com/services/unified-communications + monthly + 0.7 + + + https://queuenorth.com/services/contact-center + monthly + 0.7 + + + https://queuenorth.com/services/managed-support + monthly + 0.7 + + + https://queuenorth.com/services/consulting-training + monthly + 0.7 + + + https://queuenorth.com/services/infrastructure-cabling + monthly + 0.7 + + + https://queuenorth.com/services/wireless-access + monthly + 0.7 + + + https://queuenorth.com/services/local-networking + monthly + 0.7 + + + https://queuenorth.com/industries + monthly + 0.8 + + + https://queuenorth.com/industries/healthcare + monthly + 0.7 + + + https://queuenorth.com/industries/retail + monthly + 0.7 + + + https://queuenorth.com/industries/manufacturing + monthly + 0.7 + + + https://queuenorth.com/industries/education-finance + monthly + 0.7 + + + https://queuenorth.com/contact + monthly + 0.9 + + + https://queuenorth.com/support + monthly + 0.8 + + \ No newline at end of file diff --git a/src/main.jsx b/src/main.jsx index 3d27dcc..2fc67e2 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -3,6 +3,7 @@ import { createRoot } from 'react-dom/client' import { RouterProvider } from 'react-router-dom' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { Toaster } from 'sonner' +import { HelmetProvider } from 'react-helmet-async' import router from './router.jsx' import App from './App.jsx' @@ -17,10 +18,12 @@ const queryClient = new QueryClient({ // Wrap the router with providers const Root = () => ( - - - - + + + + + + ) diff --git a/src/pages/About.jsx b/src/pages/About.jsx index 8f7bf79..b7a5ddd 100644 --- a/src/pages/About.jsx +++ b/src/pages/About.jsx @@ -1,8 +1,19 @@ +import { Helmet } from 'react-helmet-async' import { Link } from 'react-router-dom' const About = () => { return ( <> + + About Queue North | Veteran-Owned 8x8 Partner — 25+ Years of Service + + + + + + + + {/* Page Hero */}
@@ -38,7 +49,7 @@ const About = () => {
Our Team
diff --git a/src/pages/Contact.jsx b/src/pages/Contact.jsx index 07ad52e..3769584 100644 --- a/src/pages/Contact.jsx +++ b/src/pages/Contact.jsx @@ -1,3 +1,4 @@ +import { Helmet } from 'react-helmet-async' import { useState } from 'react' import { useMutation } from '@tanstack/react-query' import { toast } from 'sonner' @@ -102,6 +103,16 @@ const Contact = () => { return ( <> + + Contact Queue North | Schedule a Consultation + + + + + + + + {/* Page Hero */}
diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx index 4f57852..252acf4 100644 --- a/src/pages/Home.jsx +++ b/src/pages/Home.jsx @@ -1,3 +1,4 @@ +import { Helmet } from 'react-helmet-async' import { Button } from '@/components/ui/Button' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/Card' import { services } from '@/data/services' @@ -16,8 +17,71 @@ const industryIcons = { const Home = () => { const navigate = useNavigate() + const organizationLd = { + '@context': 'https://schema.org', + '@type': 'Organization', + name: 'Queue North Technologies', + url: 'https://queuenorth.com', + logo: 'https://queuenorth.com/logo.svg', + description: 'Business communications and IT partner — Houghton, MI. 8x8 Certified Partner, Veteran Owned, 25+ years of service.', + address: { + '@type': 'PostalAddress', + addressLocality: 'Houghton', + addressRegion: 'MI', + addressCountry: 'US', + }, + contactPoint: { + '@type': 'ContactPoint', + telephone: '+1-906-482-6616', + contactType: 'customer service', + areaServed: 'US', + }, + sameAs: [], + } + + const localBusinessLd = { + '@context': 'https://schema.org', + '@type': 'LocalBusiness', + '@id': 'https://queuenorth.com/#business', + name: 'Queue North Technologies', + image: 'https://queuenorth.com/logo.svg', + url: 'https://queuenorth.com', + telephone: '+1-906-482-6616', + email: 'info@queuenorth.com', + address: { + '@type': 'PostalAddress', + streetAddress: 'Houghton', + addressLocality: 'Houghton', + addressRegion: 'MI', + addressCountry: 'US', + }, + areaServed: { + '@type': 'Place', + name: 'Upper Peninsula, Michigan', + }, + priceRange: '$$', + openingHoursSpecification: { + '@type': 'OpeningHoursSpecification', + dayOfWeek: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'], + opens: '08:00', + closes: '18:00', + }, + } + return ( <> + + Queue North Technologies | Business Communications & IT Partner — Houghton, MI + + + + + + + + + + {/* Hero Section */}
@@ -39,7 +103,7 @@ const Home = () => {
- 8x8 + 8x8 Certified Partner logo 8x8 Certified Partner
@@ -59,7 +123,7 @@ const Home = () => {
Communications Infrastructure
@@ -78,7 +142,7 @@ const Home = () => {
- 8x8 + 8x8 Certified Partner logo 8x8 Certified Partner
@@ -102,7 +166,7 @@ const Home = () => {
Joint Logo White - 8x8 and Cisco Partner
@@ -141,7 +205,7 @@ const Home = () => {
- 8x8 + 8x8 Certified Partner logo

8x8 Certified Partner

diff --git a/src/pages/Industries.jsx b/src/pages/Industries.jsx index f7e7c64..c770a23 100644 --- a/src/pages/Industries.jsx +++ b/src/pages/Industries.jsx @@ -1,9 +1,20 @@ +import { Helmet } from 'react-helmet-async' import { industries } from '@/data/industries' import { Link } from 'react-router-dom' const Industries = () => { return ( <> + + Industries We Serve | Queue North Technologies + + + + + + + + {/* Page Hero */}
@@ -30,14 +41,14 @@ const Industries = () => {
-

+

{industry.name} -

+

{industry.shortDesc}

-

Pain Points We Solve

+

Pain Points We Solve

{industry.painPoints.slice(0, 2).map((painPoint, index) => (
diff --git a/src/pages/IndustryDetail.jsx b/src/pages/IndustryDetail.jsx index fc90bf2..3a8bd1a 100644 --- a/src/pages/IndustryDetail.jsx +++ b/src/pages/IndustryDetail.jsx @@ -1,3 +1,4 @@ +import { Helmet } from 'react-helmet-async' import { useParams } from 'react-router-dom' import { industries } from '@/data/industries' import { Link } from 'react-router-dom' @@ -10,6 +11,10 @@ const IndustryDetail = () => { if (!industry) { return (
+ + Industry Not Found | Queue North Technologies + +

Industry Not Found

@@ -23,8 +28,22 @@ const IndustryDetail = () => { ) } + const industryTitle = `${industry.name} | Queue North Technologies` + const industryDesc = industry.shortDesc || `Learn about Queue North Technologies solutions for the ${industry.name} industry in Houghton, MI and the Upper Peninsula.` + const industryUrl = `https://queuenorth.com/industries/${industry.id}` + return ( <> + + {industryTitle} + + + + + + + + {/* Page Hero */}
@@ -89,7 +108,7 @@ const IndustryDetail = () => {
-

Industry

+

Industry

{industry.name}

diff --git a/src/pages/ServiceDetail.jsx b/src/pages/ServiceDetail.jsx index cba8bbc..c94ae42 100644 --- a/src/pages/ServiceDetail.jsx +++ b/src/pages/ServiceDetail.jsx @@ -1,3 +1,4 @@ +import { Helmet } from 'react-helmet-async' import { useParams } from 'react-router-dom' import { services } from '@/data/services' import { Link } from 'react-router-dom' @@ -10,6 +11,10 @@ const ServiceDetail = () => { if (!service) { return (
+ + Service Not Found | Queue North Technologies + +

Service Not Found

@@ -23,8 +28,39 @@ const ServiceDetail = () => { ) } + const serviceTitle = `${service.name} | Queue North Technologies` + const serviceDesc = service.shortDesc || `Learn about ${service.name} from Queue North Technologies — serving Houghton, MI and the Upper Peninsula.` + const serviceUrl = `https://queuenorth.com/services/${service.id}` + + const serviceDetailLd = { + '@context': 'https://schema.org', + '@type': 'Service', + name: service.name, + description: service.shortDesc, + provider: { + '@type': 'Organization', + name: 'Queue North Technologies', + url: 'https://queuenorth.com', + }, + areaServed: { + '@type': 'Place', + name: 'Houghton, MI / Upper Peninsula', + }, + } + return ( <> + + {serviceTitle} + + + + + + + + + {/* Page Hero */}
@@ -101,11 +137,11 @@ const ServiceDetail = () => {
-

Service

+

Service

{service.name}

-

Category

+

Category

{service.id.replace('-', ' ')}

diff --git a/src/pages/Services.jsx b/src/pages/Services.jsx index 192b144..ef8d09d 100644 --- a/src/pages/Services.jsx +++ b/src/pages/Services.jsx @@ -1,10 +1,37 @@ +import { Helmet } from 'react-helmet-async' import { services } from '@/data/services' import { MessageCircle, Users, LifeBuoy, GraduationCap, Link as LinkIcon, Wifi, Network } from 'lucide-react' import { Link } from 'react-router-dom' +const serviceLd = { + '@context': 'https://schema.org', + '@type': 'Service', + serviceType: 'Business Communications and IT Services', + provider: { + '@type': 'Organization', + name: 'Queue North Technologies', + url: 'https://queuenorth.com', + }, + areaServed: { + '@type': 'Place', + name: 'Houghton, MI / Upper Peninsula', + }, +} + const Services = () => { return ( <> + + Business Phone, UCaaS & IT Services | Queue North Technologies + + + + + + + + + {/* Page Hero */}
@@ -35,9 +62,9 @@ const Services = () => { {service.icon === 'wifi' && } {service.icon === 'network' && }
-

+

{service.name} -

+

{service.shortDesc}

diff --git a/src/pages/Support.jsx b/src/pages/Support.jsx index 0a06f7c..f8bc2a0 100644 --- a/src/pages/Support.jsx +++ b/src/pages/Support.jsx @@ -1,3 +1,4 @@ +import { Helmet } from 'react-helmet-async' import { useState } from 'react' import { useMutation } from '@tanstack/react-query' import { toast } from 'sonner' @@ -105,6 +106,16 @@ const Support = () => { return ( <> + + IT Support & Help Desk | Queue North Technologies + + + + + + + + {/* Page Hero */}