<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: MMIX Studio</title>
    <description>The latest articles on DEV Community by MMIX Studio (mmixstudio).</description>
    <link>https://dev.to/mmixstudio</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F15775%2Fa0365a05-5993-4864-a9ed-4ac3c08f896c.png</url>
      <title>DEV Community: MMIX Studio</title>
      <link>https://dev.to/mmixstudio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mmixstudio"/>
    <language>en</language>
    <item>
      <title>Lead quizzes for two trades: a Montreal cabinet painter and an Ottawa renovation contractor</title>
      <dc:creator>Tatiana Koval</dc:creator>
      <pubDate>Wed, 04 Nov 2026 21:16:38 +0000</pubDate>
      <link>https://dev.to/mmixstudio/lead-quizzes-for-two-trades-a-montreal-cabinet-painter-and-an-ottawa-renovation-contractor-671</link>
      <guid>https://dev.to/mmixstudio/lead-quizzes-for-two-trades-a-montreal-cabinet-painter-and-an-ottawa-renovation-contractor-671</guid>
      <description>&lt;p&gt;In 2026 we built lead funnels for two owner-run trades businesses: Mr. Gary, a painter in Montreal who moved into on-site kitchen cabinet painting, and Korvex Construction, a renovation and exterior construction company in Ottawa. Both started with Meta ads and a short quiz, and both later got a full bilingual site. The setups are close enough to compare, and the bugs were useful. All numbers below come from the clients' own CRM and lead logs, with test entries removed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The starting point
&lt;/h2&gt;

&lt;p&gt;Neither business had a website. Mr. Gary lived on a Facebook page, an Instagram account and word of mouth; the owner films his own jobs on his phone. Korvex had an Instagram account full of before-and-after reels, a Facebook page, reviews on HomeStars and Google, and one route for a person who wanted a price: a direct message.&lt;/p&gt;

&lt;p&gt;For a trade, speed to the lead decides most of the outcome. One of the first notes in the Mr. Gary working chat was about ad hours: run the ads when someone is free to reply within a couple of minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The quiz pattern
&lt;/h2&gt;

&lt;p&gt;Both quizzes follow the same rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;every screen fits a phone without scrolling, including the short viewport of the Facebook and Instagram in-app browser;&lt;/li&gt;
&lt;li&gt;the phone field defaults to +1 and the number on the page is a tap-to-call link;&lt;/li&gt;
&lt;li&gt;the last screen asks for contact details, after the person has already answered the job questions;&lt;/li&gt;
&lt;li&gt;a completed quiz posts into the owner's Telegram with the answers numbered and one-tap WhatsApp and Telegram links to the person.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The server side is small. The form posts to our own endpoint, which validates the payload, writes a lead log, sends the Telegram card and fires a &lt;code&gt;Lead&lt;/code&gt; event to Meta through the Conversions API with the same &lt;code&gt;event_id&lt;/code&gt; the browser pixel used. A minimal version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// browser: one id for both the pixel and the server event&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eventId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;fbq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;track&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lead&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;eventID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;eventId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/lead&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;event_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;eventId&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meta then counts each lead once and the campaign optimises on real submissions. The Telegram bot token and the Conversions API token stay on the server.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the numbers looked like
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Mr. Gary.&lt;/strong&gt; The CRM holds 92 requests between 30 March and 24 August 2026, 89 of them while the spring campaigns ran (30 March to 9 May). 82 came through the quiz and 10 through the form on the landing page. The busiest week brought 22. Quiz requests from 3 to 29 March went straight to Telegram before the CRM was connected, so they are outside that count.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Korvex.&lt;/strong&gt; The three live quizzes logged 67 leads between 14 August and 26 October 2026. The feature-wall quiz alone brought 21 leads from Meta ads in 17 days: 14 from Facebook, 6 from Instagram and 1 from Threads. The budget question in the first quiz showed the demand early: of 45 leads, 21 picked "Under $5,000" and 14 picked "Need guidance". Most people wanted fences, French drains, waterproofing and basements, done as soon as possible.&lt;/p&gt;

&lt;p&gt;The full write-ups, with screenshots of each quiz and site, are on our site: the &lt;a href="https://mmix.biz/mr-gary-cabinet-painting" rel="noopener noreferrer"&gt;Mr. Gary cabinet painting case&lt;/a&gt; and the &lt;a href="https://mmix.biz/korvex-developments" rel="noopener noreferrer"&gt;Korvex Construction case&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug that cost three leads
&lt;/h2&gt;

&lt;p&gt;On 2 October we removed one step from the Korvex premium quiz. The page changed, and the server script that receives the form still required the fields from the removed step. Three real submissions that morning were rejected and never reached the client.&lt;/p&gt;

&lt;p&gt;The fix made those fields optional. The rule we kept: a change to a quiz's steps is a change to both the page and the server script, and both are tested together in a sandbox before the change ships.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other things we would repeat
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lead forms against the quiz.&lt;/strong&gt; For a few days in April we ran a Meta lead-form campaign next to the quiz traffic for Mr. Gary. Lead forms are cheaper per request and bring more spam and more time spent warming people up, which matters most for a job that costs thousands of dollars. 82 of the 92 requests in the CRM came through the quiz.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Show the source in the lead card.&lt;/strong&gt; Since 24 October each Korvex lead in Telegram shows the platform and the ad the person came from, with the ad's picture, so the owner sees which creative brought which enquiry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the language of demand.&lt;/strong&gt; A free search study for Mr. Gary put French commercial queries about cabinet painting in Quebec at roughly three times the English ones. The spring campaigns had been English-only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build the site from the owner's own material.&lt;/strong&gt; Both sites use the owners' job photos, captions and reviews quoted word for word.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The sites that followed
&lt;/h2&gt;

&lt;p&gt;Korvex got a bilingual site on its own domain on 28 September 2026: a page per service, 23 project pages, a reviews page and 64 service-in-area pages for eight Ottawa areas. In its first month, 130 of its pages appeared in Google Search. Mr. Gary got a 116-page English and Quebec French site built around kitchen cabinet painting, with redirects from every old address already configured, waiting on a domain switch.&lt;/p&gt;

&lt;p&gt;If you run a trade and want to see what a quiz like this would ask your customers, our &lt;a href="https://mmix.biz/website-cost-estimate" rel="noopener noreferrer"&gt;website cost estimate&lt;/a&gt; is built the same way.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>marketing</category>
      <category>smallbusiness</category>
    </item>
    <item>
      <title>Moving an abandoned WordPress site without losing rankings: a checklist</title>
      <dc:creator>Tatiana Koval</dc:creator>
      <pubDate>Wed, 04 Nov 2026 21:08:23 +0000</pubDate>
      <link>https://dev.to/mmixstudio/moving-an-abandoned-wordpress-site-without-losing-rankings-a-checklist-48f2</link>
      <guid>https://dev.to/mmixstudio/moving-an-abandoned-wordpress-site-without-losing-rankings-a-checklist-48f2</guid>
      <description>&lt;p&gt;A common request in our studio: a small business has a WordPress site that nobody has updated in years. The developer is gone, the theme is from 2017, half the plugins are no longer maintained, and the host is about to switch off the PHP version it runs on. The site still brings calls from Google, and the owner is afraid that any change will lose them.&lt;/p&gt;

&lt;p&gt;Rankings drop after a move for a short list of reasons: addresses change without redirects, pages that brought visitors disappear, a staging &lt;code&gt;noindex&lt;/code&gt; ships to production, or the site was already compromised and the move carries the infection over. This checklist covers each of them. We use it on every &lt;a href="https://mmix.biz/website-rescue-and-migration" rel="noopener noreferrer"&gt;website rescue and migration&lt;/a&gt; we take on.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Take a full copy before anything else
&lt;/h2&gt;

&lt;p&gt;Copy the files, the database, the DNS zone and the list of accounts (registrar, host, email, analytics, Search Console) before you touch the live site.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# on the old host, if you have SSH and wp-cli&lt;/span&gt;
wp db &lt;span class="nb"&gt;export &lt;/span&gt;backup-&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%F&lt;span class="si"&gt;)&lt;/span&gt;.sql
&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-czf&lt;/span&gt; files-&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%F&lt;span class="si"&gt;)&lt;/span&gt;.tar.gz &lt;span class="nt"&gt;--exclude&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'wp-content/cache'&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# DNS: save the current zone, including MX and TXT records&lt;/span&gt;
dig +noall +answer example.com ANY &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; dns-&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%F&lt;span class="si"&gt;)&lt;/span&gt;.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without SSH, most hosting panels have a backup button, and a plugin such as UpdraftPlus can export to your own storage. Write down where every account lives and who holds the password. On abandoned sites the hosting account is often in the former developer's name, and recovering it takes days, so start early.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Check whether the site is already hacked
&lt;/h2&gt;

&lt;p&gt;Old WordPress installs are a common target. Before planning the move, look for signs of a compromise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;site:example.com&lt;/code&gt; in Google shows pages you never wrote (pharmacy, casino, Japanese keyword spam);&lt;/li&gt;
&lt;li&gt;PHP files inside &lt;code&gt;wp-content/uploads/&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;admin users nobody recognises;&lt;/li&gt;
&lt;li&gt;redirects that fire only for visitors coming from Google or only on phones.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find wp-content/uploads &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*.php'&lt;/span&gt;
wp user list &lt;span class="nt"&gt;--role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;administrator
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-rl&lt;/span&gt; &lt;span class="nt"&gt;--include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'*.php'&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'base64_decode'&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'gzinflate'&lt;/span&gt; wp-content/ | &lt;span class="nb"&gt;head&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you find any of this, clean the site and find the way in before you migrate, otherwise the backdoor moves with the content. Our notes on &lt;a href="https://mmix.biz/cybersecurity/hacked-website-repair" rel="noopener noreferrer"&gt;hacked website repair&lt;/a&gt; describe the order: contain, copy the evidence, clean against vendor originals, close the entry point, rotate keys, then ask Google for a review if there is a warning.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Build the list of addresses to protect
&lt;/h2&gt;

&lt;p&gt;Collect every URL that has search history or inbound links. Use four sources, because each one misses something:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the XML sitemap (often outdated on abandoned sites);&lt;/li&gt;
&lt;li&gt;a crawl of the live site;&lt;/li&gt;
&lt;li&gt;Search Console, Performance report, Pages tab, the last 16 months;&lt;/li&gt;
&lt;li&gt;server logs, if the host keeps them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pay attention to the WordPress-specific addresses that a new build usually drops: &lt;code&gt;/?p=123&lt;/code&gt; short links, &lt;code&gt;/category/&lt;/code&gt; and &lt;code&gt;/tag/&lt;/code&gt; archives, &lt;code&gt;/author/&lt;/code&gt; pages, attachment pages, &lt;code&gt;/feed/&lt;/code&gt;, paginated archives (&lt;code&gt;/page/2/&lt;/code&gt;), and direct image URLs under &lt;code&gt;/wp-content/uploads/&lt;/code&gt; that other sites embed.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Write the redirect map
&lt;/h2&gt;

&lt;p&gt;One row per old address: old URL, new URL, status code. Every old page that brought visitors or has links goes to its closest new page with a 301. Avoid sending everything to the home page; Google treats mass redirects to the home page as soft 404s.&lt;/p&gt;

&lt;p&gt;Decide the trailing slash and the &lt;code&gt;www&lt;/code&gt; question once and apply it everywhere. A mismatch between the redirect rules and the canonical tags creates chains and loops. On one recent rescue, the diagnosis found a redirect loop that turned 45% of requests into 301 responses before the site was even moved.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Test the map with a script
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# redirects.csv: old_url,expected_url&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;, &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; old expected&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nv"&gt;final&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'%{url_effective} %{http_code} %{num_redirects}'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$old&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; url code hops &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$final&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$expected&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$code&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"200"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$hops&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; 1 &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"FAIL &lt;/span&gt;&lt;span class="nv"&gt;$old&lt;/span&gt;&lt;span class="s2"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="nv"&gt;$code&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="nv"&gt;$hops&lt;/span&gt;&lt;span class="s2"&gt; hops), expected &lt;/span&gt;&lt;span class="nv"&gt;$expected&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;fi
done&lt;/span&gt; &amp;lt; redirects.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it against staging with the production hostnames mapped in &lt;code&gt;/etc/hosts&lt;/code&gt;, and again right after launch. Every line it prints is a page that will lose its rankings.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Launch-day checks
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Remove &lt;code&gt;noindex&lt;/code&gt; and the staging &lt;code&gt;Disallow: /&lt;/code&gt; from robots.txt. This is the most common way a migration wipes a site from Google, and it is easy to check with &lt;code&gt;curl -s https://example.com/robots.txt&lt;/code&gt; and a look at the page &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Canonical tags point at the new production URLs.&lt;/li&gt;
&lt;li&gt;hreflang pairs still match if the site has English and French.&lt;/li&gt;
&lt;li&gt;Forms, phone links and analytics events work the way a customer uses them. Send a real test request.&lt;/li&gt;
&lt;li&gt;Submit the new sitemap in Search Console and keep the old one for a few weeks so Google recrawls the redirects.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. Watch the first weeks
&lt;/h2&gt;

&lt;p&gt;Check Search Console coverage and 404s daily for the first week and again after three or four weeks. A short dip while Google recrawls is normal. A page that stays gone is usually a missing redirect, and the fix is one line in the map.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repair or rebuild?
&lt;/h2&gt;

&lt;p&gt;Some abandoned sites come back with updates and a cleanup. Others cost less to rebuild on a current stack than to keep patching. Make the choice with both options and their scope written down, after the copy and the diagnosis. If you are in Canada and want us to do it, the &lt;a href="https://mmix.biz/website-rescue-and-migration" rel="noopener noreferrer"&gt;website rescue and migration&lt;/a&gt; page has a short quiz that asks for the address of your site.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>webdev</category>
      <category>seo</category>
      <category>devops</category>
    </item>
    <item>
      <title>What a small-business website costs in Canada in 2026 and how to estimate yours</title>
      <dc:creator>Tatiana Koval</dc:creator>
      <pubDate>Wed, 04 Nov 2026 21:08:15 +0000</pubDate>
      <link>https://dev.to/mmixstudio/what-a-small-business-website-costs-in-canada-in-2026-and-how-to-estimate-yours-52bo</link>
      <guid>https://dev.to/mmixstudio/what-a-small-business-website-costs-in-canada-in-2026-and-how-to-estimate-yours-52bo</guid>
      <description>&lt;p&gt;Most small-business owners in Canada ask the price of a website first and describe the website second. The number they get back depends almost entirely on the second part, so two quotes for "a site for my company" can differ by a factor of ten and both be honest.&lt;/p&gt;

&lt;p&gt;We price websites for service businesses in Ottawa and across Canada, and we built a &lt;a href="https://mmix.biz/website-cost-estimate" rel="noopener noreferrer"&gt;website cost estimate quiz&lt;/a&gt; around the six questions we need answered before we can give a figure in writing. This post walks through those questions and what each one does to the cost, so you can estimate your own project before you talk to anyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. What is the job?
&lt;/h2&gt;

&lt;p&gt;A new site, a redesign of a site you have, a rescue of a site that is broken, hacked or locked away from you, an online store, or a site that takes bookings. These are five different projects.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A first site has nothing to protect. It starts faster.&lt;/li&gt;
&lt;li&gt;A redesign or a rescue starts with an inventory of the old site: every address that has search history or links pointing at it needs a redirect to its new page.&lt;/li&gt;
&lt;li&gt;A store or a booking site adds integrations that have to be built and tested: payments, taxes, shipping, a calendar tied to staff schedules, reminders.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Who is the site for?
&lt;/h2&gt;

&lt;p&gt;A local service business, a shop, a professional practice, a non-profit and a personal project need different pages. A plumber in Kanata needs service pages, area pages, a phone button and a short form. A law practice needs one page per practice area and an intake form that screens the request. The page list drives the cost more than the design does.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. What must the site do?
&lt;/h2&gt;

&lt;p&gt;Write down the jobs, one per line:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;be found on Google and in AI answers for your services and your city;&lt;/li&gt;
&lt;li&gt;bring enquiries by phone, form, WhatsApp or a quiz;&lt;/li&gt;
&lt;li&gt;take bookings or payments;&lt;/li&gt;
&lt;li&gt;work in English and French;&lt;/li&gt;
&lt;li&gt;answer common customer questions with an assistant.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each line is a set of pages or an integration. English and French is a good example: a bilingual site is two sites to write and check, plus the hreflang tags and URL structure that keep the two languages apart in search. In Ottawa, with Gatineau across the river, many service businesses need both.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. What does it run on today?
&lt;/h2&gt;

&lt;p&gt;This question decides how much work goes into protecting what you already have. If the current site gets visitors from Google, export the list of pages from Search Console before anyone touches it. Here is a short script we use on that export (Performance report, Pages tab, Export as CSV):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="c1"&gt;# usage: python pages_to_keep.py Pages.csv
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DictReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;keep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Clicks&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Impressions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;keep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Clicks&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;keep&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Clicks&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;rjust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Impressions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;rjust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Top pages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; of &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; addresses need a redirect or must keep their URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last line of output is a good proxy for the migration part of the estimate. A site with 12 such pages and a site with 900 are different jobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Copy and photos
&lt;/h2&gt;

&lt;p&gt;Copy takes longer than code. A site built from text you already have is cheaper than one where the studio writes every service page. Real photos of your work, your team and your premises do more for enquiries than any layout, and if you do not have them, a shoot becomes a line in the estimate.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Budget band and timing
&lt;/h2&gt;

&lt;p&gt;Giving a budget band feels like a negotiating mistake. In practice it decides which scope the studio proposes: the same goals can be met with a one-page site and a quiz, or with forty service and area pages. Say the band and ask what fits inside it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ongoing costs to ask about
&lt;/h2&gt;

&lt;p&gt;The build is one number. Ask separately about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hosting and platform fees.&lt;/strong&gt; Builder plans such as Wix or Squarespace run CA$50 to CA$100 a month for as long as the site lives. A static site on a CDN costs close to nothing to host for a typical small business.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Updates.&lt;/strong&gt; A WordPress site needs core, theme and plugin updates, and each update can break something.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search, ads and content after launch.&lt;/strong&gt; These should be quoted separately so you can stop any of them without losing the site.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tax.&lt;/strong&gt; In Ontario, HST at 13% goes on top of a quoted price.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What our published prices are
&lt;/h2&gt;

&lt;p&gt;So you have real numbers to compare against: rebuilding an existing old WordPress, Joomla or website-builder site as a fast static site you own costs CA$500 + HST for a one-page site and CA$1,000 + HST for a multi-page site, paid once, on our &lt;a href="https://mmix.biz/website-rescue-and-migration" rel="noopener noreferrer"&gt;website rescue and migration&lt;/a&gt; offer. A 30-minute marketing strategy session costs €100. New sites, stores and booking sites are quoted per scope; our &lt;a href="https://mmix.biz/pricing" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt; explains how that quote is put together.&lt;/p&gt;

&lt;h2&gt;
  
  
  A short checklist before you ask for quotes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The job: new, redesign, rescue, store or bookings.&lt;/li&gt;
&lt;li&gt;The page list, one line per service and per area.&lt;/li&gt;
&lt;li&gt;The languages.&lt;/li&gt;
&lt;li&gt;How a request reaches you: call, form, quiz, booking.&lt;/li&gt;
&lt;li&gt;The Search Console export of the current site, if there is one.&lt;/li&gt;
&lt;li&gt;Who owns the domain, the hosting and the analytics today.&lt;/li&gt;
&lt;li&gt;Your budget band and the date you want to launch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With that list in hand, any studio can give you a written figure, and you can compare quotes line by line. If you want ours, the &lt;a href="https://mmix.biz/website-cost-estimate" rel="noopener noreferrer"&gt;two-minute quiz&lt;/a&gt; asks exactly these questions and a person from the studio replies with a written estimate.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>smallbusiness</category>
      <category>beginners</category>
      <category>career</category>
    </item>
  </channel>
</rss>
