Skip to main content
Jonathan Andrei
Back to all posts
Feb. 2026 - Present6 min read

Shipping a Bilingual Marketing Site That a Quebec Team Can Own

Roofing and solar buyers don't read marketing copy linearly. They scan for trust signals, hours, and 'does this person know what I have on my roof?' The AL Pro build is tuned for that, bilingual, scannable, and SEO-hardened against AI-driven audits.

ReactViteSEOi18nQuebec
AL Pro Solutions bilingual marketing site hero, showing the roofing and solar services brand.
The AL Pro Solutions homepage: bilingual hero, service selector, and a feasibility-study CTA above the fold.

Quebec roofing buyers behave differently from the marketing-blog stereotype. They're scanning the page for: do you understand my building, do you serve my city, can I get a feasibility study, and is your French copy actually French or is it Google Translate.

Why I built it

The existing site was a WordPress theme with a French toggle that was 80% English under the hood. The contact form lived three clicks away from the hero. The audit tool the team paid for scored the site 61/100 and listed missing structured data, missing meta, and thin content on pages that visibly had content. I took the project to rebuild the front end as a bilingual SPA with proper prerendering, then bolt WordPress back on for the blog only.

What it does

The site is a bilingual marketing front for a roofing and solar contractor serving Greater Montreal. It surfaces services per building type, lists past projects per city, and routes every CTA to a single feasibility-study form that captures roof type, square footage, and target install window.

  • Bilingual EN/FR routes with locale-aware metadata and hreflang.
  • Per-city service pages, each with its own areaServed schema.
  • Project gallery filtered by building type (residential, commercial, industrial).
  • Lead form that posts to the team's CRM with the form locale attached.
  • WordPress headless feed for blog posts, queried at build time.
Diagram of the AL Pro Solutions onboarding process, from feasibility study to install.
The process section explains the buyer journey in five steps, mirrored in French with the same illustrations.

Architecture decisions

A React SPA built on Vite gave the brand the animations and the lead-capture polish they wanted, while a WordPress integration on the side gives the team a friendly content workflow for blog posts and updates. The split (SPA for the hero/services/projects, WordPress for evolving content) keeps the marketing site fast without making the team learn React.

I picked Vite over Next because the team did not want server hosting and was happy on a static CDN. Prerendering via vite-plugin-prerender writes one HTML file per route, including French variants, so a crawler that never executes JavaScript still sees the full page. WordPress runs on its own subdomain and is pulled in at build time via the REST API, so the production site has zero runtime dependency on the CMS being up.

The SEO audit that retargeted the build

An external auditor scored the site 61/100, with most of the points lost on items that were actually present but invisible to a non-JS crawler. The fix wasn't to argue with the audit, it was to make sure a re-audit could find the content without executing JavaScript. I added per-route prerendering, a noscript layer with real content, FAQPage and BlogPosting JSON-LD, an OfferCatalog of services with city-level areaServed, and a sitemap that regenerates from blog data at build time.

Estimated score after the rework: 92 to 96. The bigger lesson: AI-driven audit tools read the rendered HTML, not the React shell. If you want them to grade you on what's actually on your site, you have to ship the content in the HTML.

Project gallery on the AL Pro Solutions site showing completed roofing and solar installations.
The projects grid: each card carries city, building type, and roofing system as filterable metadata.

What was hard

Bilingual routing was the part I underestimated. Every page had to exist twice with stable URLs (/services/toiture and /en/services/roofing), and the language switcher had to land on the equivalent page, not the home. I ended up keeping a small route map keyed by a stable slug so the toggle knows that /projets/montreal maps to /en/projects/montreal even when the localized slugs diverge.

The other hard part was getting the WordPress feed and the prerender step to agree on timing. The prerender runs during the build, so a blog post published five minutes after a deploy would not appear until the next build. I added a webhook on the WordPress side that triggers a redeploy when a post is published, which keeps the static site within a few minutes of the CMS.

What I learned

Auditors and search engines do not reward intent, they reward bytes. The same content that scored 61 in a React shell scored in the low 90s once it was sitting in the prerendered HTML. The second lesson is that French copy written by a native Quebec speaker reads completely differently from translated marketing English, and the lead form completion rate on the French side noticeably tracks that quality.

What's next

  • Per-city landing pages prerendered from a single template, one per service area.
  • A solar savings calculator that posts the result into the feasibility form.
  • A CRM webhook that scores leads by city and roof type before they hit the inbox.
  • A re-audit pass to confirm the estimated 92 to 96 score in the real tool.
If you ship a React marketing site and the audit complains about missing content, the answer is almost always prerendering plus a real noscript layer, not more meta tags.
Related project

AL Pro Solutions: Commercial Roofing & Solar Marketing Site

View the project