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:

  1. 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.
  2. 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

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.