Most tutorials about "local business websites" assume a CMS, a theme and a dozen plugins. We went the other way for a small locksmith shop in Columbia, Missouri, and the result has been easier to maintain than anything with a database behind it. Here is what we built for OMW Locksmith LLC and the three decisions that mattered.

1. Plain HTML pages, uploaded over the host's API

The whole site is static HTML and CSS on shared hosting. No build step, no npm, no admin panel to patch. New pages (including blog posts) are written as a single index.html inside a folder such as /blog/<slug>/ and pushed with the hosting provider's file API. A deploy is one HTTP request; a rollback is re-uploading the previous file.

Why it matters for a service business: the phone rings when the page loads fast on a cracked phone screen at 11 pm in a parking lot. A 7 KB HTML file with no render-blocking scripts does that reliably. Lighthouse mobile scores stay in the high 90s without any tuning.

2. One LocalBusiness JSON-LD block, and the same data everywhere

The home page carries a single JSON-LD block of type Locksmith (a subtype of LocalBusiness) with the business name, street address, phone number, service area and opening hours:

{ "@context": "https://schema.org", "@type": "Locksmith", "name": "OMW Locksmith LLC", "telephone": "+1 (636) 201-1375", "address": { "@type": "PostalAddress", "streetAddress": "2210 Whitegate Dr", "addressLocality": "Columbia", "addressRegion": "MO", "postalCode": "65202" }, "areaServed": ["Columbia", "Ashland", "Fulton", "Moberly", "Kingdom City"] } 

The rule we enforce in code review: the name, address and phone in the JSON-LD must be byte-for-byte identical to the ones in the footer, the contact page and every directory listing. Search engines reconcile a business across the web by matching those three fields, and a stray "Suite 2" or a second phone format quietly splits the entity in two. Keeping the NAP in one place and copying it verbatim is boring, and it is the single highest-leverage SEO decision on a site like this.

3. Click-to-call as the primary action

Every page has one obvious action: <a href="tel:+16362011375">. Not a contact form, not a chat widget. For emergency trades (lockouts, lost car keys) the conversion is a phone call, so the button sits in the header, in the hero and at the end of every article, and it is large enough to hit with a thumb. We track it with a single click listener that fires an analytics event; nothing else runs on the page.

The blog without a CMS

Articles are written in Markdown, converted once to HTML with a tiny script, wrapped in the site's layout and uploaded as /blog/<slug>/index.html. There is no comment system and no admin login to brute-force. The first post explains what drives the price of a locksmith call in mid-Missouri; you can see the output of the pipeline here: what locksmith work actually costs in Columbia, MO.

What we would tell another developer

  • Static is enough for the vast majority of service businesses. Reach for a CMS only when a non-technical person has to edit weekly.
  • Put the NAP in one source file and generate every page from it. Consistency beats cleverness.
  • Measure on a real mid-range phone over cellular, not on your laptop over fiber.
  • Ship the tel: link first; everything else is secondary.

If you have built something similar, I would like to hear what you used for the HTML pipeline and whether you kept it static as the site grew.