We had a feeling about our own website, and a feeling is a hard thing to act on.
The home page and the story page felt like the same story told twice. Read one, then the other, and you met Nigeria again, the open source tools again, the institute we are building again. Nothing was wrong in isolation. Together they sagged. But when you try to fix a thing by feel, every cut is an argument about taste, and taste is slow and personal and easy to defend against. We wanted something firmer than a feeling. We wanted evidence.
So we did what we would do with any tangle in a codebase. We stopped reading the words as words, and started treating them as data.
Content is data wearing a nice outfit
A web page looks like prose. Underneath, it is structure. Our pages are built from blocks: a hero, a section of text, a grid, a timeline. Each block is really a small record. It has a type, a position, a heading, and a job to do. Seen that way, a page is not an essay. It is a list of rows.
That shift is the whole trick. Once content is data, you can ask questions of it that you cannot ask of prose. You can count. You can group. You can find the thing that appears where it should not. A vague sense of repetition becomes a query with a yes or no answer.
The unit we were missing had no name on the page. We called it a beat. A beat is a single idea the site is trying to land. Our roots in Nigeria are a beat. Our open source engine is a beat. The institute we are growing into is a beat. A section is just one way of presenting a beat, and the same beat can be presented on more than one page. That last sentence is where the trouble was hiding.
A throwaway database, gone by lunch
We sketched a tiny database. Four tables, fifteen lines of schema, the kind of thing you delete the moment it has answered you. One table for pages, one for beats, one for the sections on each page, and one join table to connect them. The join table is the quiet hero here. It records which beats each section is telling, and at what depth, lite or full.
CREATE TABLE page (id, slug, job);
CREATE TABLE beat (id, name, owner_page); -- where this idea is told in full
CREATE TABLE section (id, page_id, position, heading);
CREATE TABLE section_beat (section_id, beat_id, depth); -- the join
Then we typed our two pages into it, every section, every beat each section carried. Tedious for ten minutes. Worth it. Because now the question we had been circling for an hour was one short query.
SELECT beat, count(distinct page_id) AS pages
FROM section_beat JOIN section USING (section_id)
WHERE depth = 'full'
GROUP BY beat
HAVING pages > 1;
In plain words: show me every idea we are telling in full on more than one page. The database did not have feelings about our website. It returned three rows. Our Nigerian roots. Our open source engine. The institute we are building. Each one told fully, twice. The repetition we had felt was now three named things on a screen, and you cannot argue with three named things the way you can argue with a feeling.
The fix has an old name
What we had was a normalisation problem, the same one that haunts any database where the same fact is stored in two places. When a fact lives twice, the two copies drift, they contradict, and the reader, or the program, stops trusting either. The cure is old and simple. Store each fact once, in one home. Everywhere else, point to it.
So we gave every beat a single home. The roots, the engine, the longer arc of the institute belong to the story page, the place a reader goes when they want the full account. The home page keeps only a light touch of each, a single line and a link, and spends its space on the job only it can do, which is to say what we offer and show people the door they came for.
A second query, almost the same as the first, then told us which sections to cut. Any section telling a beat in full on a page that does not own it. Three sections on the home page lit up. We removed them. The home page went from nine sections to six and got faster to read and easier to act on. The story page kept the depth and stopped competing with the front door for the same words.
We did not design that outcome by argument. We let the structure show us, and we followed it.
Why a school would bother
We could have moved some paragraphs around until it felt better. It would have taken the same afternoon. The reason we reached for code instead is the reason we exist as a school.
We teach a way of working, not a stack of facts. And the habit underneath this small job is one of the most useful a builder can own. Make the implicit explicit. When something is hard to reason about, change its form until it is easy. A wall of prose is hard to reason about. A table is easy. The skill is not knowing SQL. It is recognising that a question about words could become a question about rows, and being willing to spend ten honest minutes building the thing that answers it.
That habit settles arguments that taste cannot. It replaces the loudest voice in the room with a result anyone can reproduce. It is, quietly, a fairer way to make decisions, and we think a school that builds technology for social change should model fair decisions in the small things as well as the large ones.
We also do this in the open on purpose. This website is part of how we earn trust, from the communities we build with and from the people who fund the work. Showing the working, including the throwaway database we deleted before lunch, is more honest proof of how we think than any claim we could write about ourselves. We are building a coding institute, and we are building it where the working can be seen.
The website told the same story twice. We did not talk ourselves out of the feeling, and we did not act on it blind. We gave it a shape we could question, and then we listened to the answer.



