Onboarding a developer
Read this end to end before touching anything. It is short on purpose, and every rule in it exists because breaking it has already cost us something real.
#What this business actually does
Most small firms pay an agency for SEO every month and never see it working — no data, no proof, no idea what changed. Majestic One is the platform that fixes that. It watches a client's Google Search Console, Analytics, Business Profile and rankings every day, works out the highest-impact change, and — once a person has approved it — makes that change on the client's live website and measures whether it worked.
Two consequences shape everything you'll do:
- We own the client's website. We don't bolt a plugin onto their WordPress; we rebuild the site as clean, fast, static code that the platform can write into. That's why the sites are ours to build and why they all share a contract.
- Everything must be provable. Figures come from Google and the client's own site. We do not estimate and present it as fact, and we never invent anything.
#The map
| What | Where it lives | Deploys to |
|---|---|---|
| The platform (dashboard, AI, ingestion) | seo-platform |
Vercel → portal.majesticone.co.uk |
| Client websites (~12) | one repo each, e.g. hal-construction-site |
Cloudflare Pages → the client's domain |
| Starting point for a new client site | client-template |
— |
| Tools for migrating an existing site | migration-kit |
— |
| Externally shared pages (brochures, client reports) | majestic-one-share |
reports.majesticone.co.uk |
| Internal manuals — including this one | majestic-one-instructions |
docs.majesticone.co.uk |
Client sites are Astro. The platform is Next.js. Everything is on GitHub.
#Day one
You'll be given: a platform login, GitHub access, and the client site you're starting on.
git clone https://github.com/majesticonemain-a11y/<repo>.git
cd <repo>
npm install
cp .env.example .env # fill from the platform: site page → "Connect this site"
npm run dev
The repo is the website. Nobody uploads a site — you push code, Cloudflare builds it. Your local folder is a disposable working copy. Anything you haven't pushed exists only on your laptop, so push at the end of every session.
.env is gitignored and will never come back from a clone. Production values live in Cloudflare
Pages, set by migration-kit/setup-client.mjs.
#Building a client site — two paths, pick deliberately
Migrating an existing site (client already has one, and it ranks) → use migration-kit.
prerender.mjs crawls the live site, de-duplicates pages that render the same content under
several URLs, and renders flat static HTML with a sitemap and redirects. Read that README first.
Building fresh (new business, or an agreed redesign) → clone client-template. Fill
src/data/site.ts, run npm run import to seed the platform, then restyle.
#Never build a client site from scratch
The platform writes content into these sites. Every section type it can send must exist in
the renderer, or the page renders blank — not broken, blank, which nobody notices until a
client does. client-template satisfies that contract. A bespoke site built from nothing does
not.
Restyle the template as much as you like. Don't replace its plumbing.
#The workflow
git checkout -b descriptive-branch-name
# work, commit as you go
git push -u origin descriptive-branch-name
Open a pull request. Cloudflare Pages builds every branch to its own preview URL, so the PR
gets a link to the real, rendered site. That link is the review — not the diff. Oliver looks at
the page, then merges. Merging to main deploys to the live domain.
Never push straight to main on a client site. A push is a deploy.
#Rules that don't bend
Never invent a trust claim. Not reviews, review counts, ratings, testimonials,
accreditations, certifications, insurance, memberships, years in business, project numbers or
case studies. Every one of those comes from the client. This is strictest on fra-dorset-site,
which is fire safety, and it applies to anything AI-generated. If you need a number and don't
have it, ask — do not estimate into a client-facing page.
Preserve URLs when rebuilding. Check Search Console before you decide the structure. On FRA, the obvious structure would have 404'd 42 of 43 live URLs, including a homepage with 34,000 impressions. The ranking pages were rebuilt at their original URLs instead. Rebuild to the old URLs; don't redirect to new ones.
Check what a page ranks for before changing its title. A homepage title rewrite lost HAL the "construction company" position it held. The platform now warns you, and won't let a non-admin approve a change it flags — but the habit matters more than the guardrail.
Secrets never enter the repo. .env is gitignored, .env.example holds placeholders. Real
values live in Cloudflare and Vercel only. Never paste one into a chat, a commit, a ticket — or
a command line, where your shell saves it to history and any process can read it from ps.
PUBLIC_* vars are the exception — they're inlined into the browser bundle by design and are
meant to be visible. See docs/secrets.md for what each secret protects and how to rotate one.
Read the config before you quote it. Node version, build command, env var name, package
version — open package.json / astro.config.mjs and look. A guessed config value is a broken
deploy.
Structural things stay in the template. Icons, slugs, image paths, schema, template type, robots and sitemap URLs must never come from editable page data, because an AI content rewrite can wipe them. This has caused production bugs more than once.
#Things that are already protecting you
scripts/verify-build.mjs is wired into npm run build on every client site. It blocks the
build on broken internal links, blank pages, and cross-domain contamination — it once caught a
bug where all eleven sites advertised the wrong site's sitemap. If it fails your build, it is
almost certainly right.
migration-kit/setup-client.mjs configures a Pages project in one command: env vars, apex
custom domain, email-obfuscation off, rebuild. It prints the handful of steps that genuinely
can't be scripted.
migration-kit/verify-analytics.mjs loads a deployed page in a real browser and asserts the
GA4 hit actually fires. It catches "tag present but never sends", which curl cannot.
#What you won't have access to, and why
- Cloudflare DNS / nameservers. Moving a domain's nameservers can kill the client's email, not just their website. FRA's mail runs on Hostinger; a careless cutover loses it. A website rolls back in two minutes, lost email doesn't.
- Production secrets — database URLs, API keys, OAuth tokens.
- Deleting leads, connecting ad accounts, managing users in the platform. A lead is the proof the whole service works.
- Approving a change the platform flags as risking a live ranking. You can always reject it, and rewording it to keep the ranking phrase is usually the better answer anyway.
None of this is about trust. It's that these are the actions where a mistake can't be undone from the interface.
#When something looks wrong
Check it in a browser before you believe it. Several of the worst bugs this project has shipped were invisible in the code and obvious on screen: white text on a white background, an image at the wrong aspect ratio, a booking form with no handler that silently did nothing.
If a number looks too good, check it against Search Console. The rank tracker once reported a page at #1 that was nowhere in a real search.
Say when something failed. "I tried X, it did Y, I don't know why" is a completely fine thing to report, and much cheaper than the alternative.
Internal documentation. Not for clients, and not for search engines — this page sends noindex, but the link itself is the only thing keeping it private.