AI productivity
A post dated in the future does not exist yet
A beautiful publishing pipeline with nothing worth saying in it is a very expensive way to be ignored.
We publish three times a week on two sites and we do not run a content management system. There is no admin login, no database and no plugin waiting to be patched. This is what we built instead, and the half of it that happens before anybody touches a keyboard.
It does not start with typing
Almost every article about publishing starts at the keyboard, which is why almost every article about publishing is useless. The keyboard is the last part.
Here it starts out loud. Thinking through something that happened during the week, dictated rather than typed, usually not at a desk, often lying down, frequently at an hour nobody would describe as working. No structure, no headings, no attempt to be clever. Just the whole of a thought said once, including the parts that turn out to be wrong.
Then it comes back as something written, and the real work starts, which is reading it. Where is the argument thin. What is being claimed that cannot be backed. Which sentence is doing nothing. Which paragraph is true but is somebody else's sentence rather than mine.
Then it goes round again. And again. A post of this length is read properly three or four times, and the corrections are rarely about grammar. They are about a premise that was wrong, a point that was missing, a punchline that had already been used.
Only when the material is finished in the human layer is there anything worth publishing. Everything below this line is plumbing. It matters, it is genuinely good engineering, and it is still plumbing, and that order is not decoration.
The machine makes drafts quickly. Deciding whether a draft is true is not a thing that can be made quicker, and it is the entire job.
And then it has to come out on a Wednesday
The writing happens in one sitting. The posts have to appear on Monday, Wednesday and Friday, whether or not anybody is at a computer on those days.
That is a solved problem. Every content management system on earth has a publish date field. The question was whether we wanted a content management system in order to get one.
We do not run a CMS. We built the blog instead.
This is the part I want to be direct about, because it is the whole argument of this company applied to our own website.
There is no WordPress here. No admin area, no user accounts, no database, no theme, no plugins. The blog is a few files that we wrote and that a person can read in an afternoon. One file holds the posts. One script turns them into pages. One script checks the result and refuses to publish if it does not like what it sees.
It is auditable. Every rule about what appears on this site is written in code we wrote, in files we can open. There is nothing whose behaviour we have to guess at, no setting buried four menus deep that somebody changed last year, no plugin whose author stopped maintaining it in 2019.
There are no emergency updates. This is the one people underestimate. Running a content system means a permanent obligation to patch it, urgently, on somebody else's schedule, because a vulnerability was announced and your site is now publicly known to have it. That obligation never ends and it never arrives at a convenient hour. We do not have it, because we are not running the software that creates it.
Maintenance is reading, not firefighting. Nothing here updates itself in the night and breaks the layout.
A system you can read is cheaper than a system you have to trust.
And the infrastructure it sits on
The built site is static files served through CloudFront. There is no application server answering requests, so there is nothing to log into and nothing holding a session. The storage behind it is not publicly writable.
The things that typically take over a small company's website, a stolen admin password, an unpatched plugin, an injected script in a theme, are not merely defended against here. They are not present to be attacked.
I will not write the sentence that says this makes us unbreakable, because that sentence is always written by somebody who is about to be broken. What is true is narrower and better: the surface is small, we know exactly what is on it, and we can read all of it.
And the location is a decision rather than a default. Where this runs, and where anything we store lives, is chosen, in Europe, on purpose, because for the other things this company builds that is not a preference, it is the law.
The whole feature, in one function
A post has a date. A post exists if its date is not in the future. That is all of it.
export function publishedPosts(today = todayInVienna()) {
return blogPosts.filter((post) => post.date <= today);
}
Dates are written as 2026-10-12, as strings, and compared with a less-than sign. That looks lazy and it is deliberate. In that format, and only in that format, alphabetical order and chronological order are the same order, so the comparison a computer does for free is already the comparison we want. No date library, nothing to parse, nothing that can disagree with itself about what day it is.
Vienna is not UTC, and that is where this breaks
The build runs on a machine in a data centre, and that machine thinks in UTC. We are in Vienna, which is one or two hours ahead depending on the season.
So for the first hour or two of every day, a server asked what today is would answer yesterday. A post dated Monday would still be invisible at one in the morning in Vienna on Monday, which is exactly the kind of fault that looks like nothing is wrong.
export function todayInVienna() {
return new Intl.DateTimeFormat('en-CA', {
timeZone: 'Europe/Vienna',
year: 'numeric', month: '2-digit', day: '2-digit',
}).format(new Date());
}
The en-CA is not a joke and it is not about Canada. That locale formats dates as year, month, day, with dashes, which is the exact string shape the comparison above needs. It is already in the browser and in Node, it knows when Austria changes its clocks, and it costs nothing to load.
One function, read by five different things
This is the part I would keep even if you threw away everything else here.
Five places on these sites have an opinion about which posts exist. The blog index. The article page, which has to return a proper 404 rather than a page for a post that is not out yet. The prerenderer, which writes one HTML file per page. The sitemap, which tells search engines what to look at. And the endpoint behind the little heart, which only accepts a vote for a post that has been published.
All five call the same function. Not five similar checks that have to be kept in agreement. One, imported five times.
A post that is missing from the index and present in the sitemap is not a small inconsistency. It is a page Google will try to fetch and be given a 404 for, on a site whose entire argument is that it was built carefully.
Five copies of the same rule is five chances to fix four of them.
Something has to happen on the day
Here is the part that is easy to miss. The site is a set of files that were built at some point in the past. Nothing re-evaluates anything. If Monday arrives and nobody rebuilds, Monday's post does not appear, because no machine anywhere has noticed that the date changed.
So the site rebuilds itself every morning.
on:
push:
branches: [main]
schedule:
- cron: '0 5 * * *' # 07:00 Vienna in summer, 06:00 in winter
Note what this is not. It is not a second workflow that publishes posts. It is a second reason to run the one that already exists, so a scheduled publication goes through precisely the same build, the same checks and the same refusal to deploy if anything fails. A separate publishing path would have been a path nobody tests on an ordinary day.
And one thing written in no tutorial: GitHub switches a scheduled workflow off after sixty days without activity in the repository. It does not warn you. If you write regularly you will never see it. If you go quiet for two months, the first thing that fails is the thing that was supposed to work while you were quiet. Pressing Run workflow once turns it back on.
The limitation, printed underneath the feature
Now the part most write-ups would end without mentioning.
All the posts live in one JavaScript file, and that file is compiled into the site's bundle as a whole. So the text of a post dated next Friday is sitting inside the JavaScript your browser downloads today.
Being precise about what that does and does not mean. There is no page. No link anywhere points to it. It is not in the sitemap, so no search engine is told about it. The article route refuses it and returns a 404. The heart will not accept a vote for it. By every ordinary route, through the site or through Google, it does not exist.
And a person who opens the developer tools and reads a minified bundle can find it.
We kept it, deliberately, and the reason is written in a comment next to the code so the next person to read it inherits the decision rather than rediscovering it. Nothing we schedule is confidential. It is a blog post coming out on Friday. The alternative is splitting the data file so each post loads separately, which is real work, real complexity and a permanent maintenance cost, bought in order to protect a company blog post from being read three days early by somebody who would be welcome to read it on Friday.
If what you are scheduling is an earnings release, a product launch under embargo, or anything a competitor would pay for, do not do it this way. That sentence is the useful part of this whole section, and it exists only because we wrote the limitation down instead of admiring the feature.
A limitation you publish is a decision. A limitation you leave out is a surprise with a date on it.
What this took, and what the machine could not do
An afternoon. The function is four lines, the date helper is six, the workflow change is two, and most of the afternoon went on the five places that call it and on proving the thing in both directions.
I built it with an AI assistant, and I want to be accurate about which half was which, because a great deal of nonsense is being written about this at the moment.
What it was genuinely good at. Writing the function. Finding all five call sites and missing none. Knowing that en-CA produces the required format, which is the kind of fact that costs a person twenty minutes to confirm. Writing the comments and the runbook afterwards, properly, which is the work everybody skips and the work that matters in six months.
What it did not do. It did not tell me that the data file is bundled whole, until I asked what happens to a scheduled post's text. It answered honestly and immediately once the question existed. But the question had to exist, and nothing in the task of building the feature produces it.
That is the actual shape of this. It will build what you ask for, and build it well. It will not tell you what you should not have asked for. The reviewing did not go away, it moved. Instead of writing forty lines I spent the afternoon deciding whether they were the right forty, which is a better use of an engineer than typing, and it is the same skill as reading a draft and knowing which paragraph is a lie.
A real example, because the general version is too comfortable
A few days ago I wanted to know whether Google had actually indexed these pages. Search Console was still empty and I wanted a second opinion.
So I asked, and the answer came back with a search. The search returned results that had nothing to do with my site, and the conclusion drawn from them sounded reasonable. I did not accept it. I said it again, because I know what that console is for and I knew the answer I had been given could not have come from anywhere real.
The correction, once it came, was the right answer and it was an admission: that tool cannot answer that question at all. Not badly, not approximately. A general web search cannot tell you what a specific search engine has indexed for your domain. The only instrument that answers it is URL Inspection inside Search Console, which asks Google directly and gets Google's own record back.
That is the failure mode worth knowing about, and it is not the one people are warned about. The danger is rarely an answer that is obviously nonsense. It is a plausible answer produced by a tool that was never capable of answering, delivered in exactly the same tone as a correct one. Nothing in the output marks the difference.
What closed the gap was not cleverness. It was twenty-eight years of knowing what a tool is for. I knew what Search Console measures, so I knew the answer was impossible, so I pushed. Somebody without that background would have written the answer down and moved on, and would have been wrong for weeks without ever finding out.
This is the thing I would say to anybody being sold the idea that the expertise is now optional. The expertise is what tells you when to stop believing the output. It has not been automated. It has been promoted.
It was not a wrong fact. It was the wrong instrument, used confidently, and confidence is the part that does the damage.
If you want this on your own site
Four steps, and none of them need a content management system.
One. Give every post a date as a plain YYYY-MM-DD string.
Two. Write one function that filters by it, in your own timezone, not the server's.
Three. Find every place in your code that lists or looks up a post and make all of them call that one function. This step takes the time and this step makes it correct.
Four. Add a daily schedule to the deployment you already have. Not a new one.
Then test it in both directions, which is the step people skip: build with a date in the past and confirm the post is there, build with a date in the future and confirm it is absent from the index, the article page, the sitemap and the heart.
Why we write these down
This is a company that advises other people on how their systems are built, so the honest thing is to publish how ours are, including the parts we chose not to solve.
A forty line feature with a documented limitation is a more useful object than a perfect feature described vaguely. You can read this one, disagree with the trade and make a different call on your own site, which is the only kind of technical writing that was ever worth anybody's time.
Scheduling is easy. Deciding what the scheduled thing is allowed to be is the engineering, and that part still happens out loud, before anybody touches a keyboard.
MK!
Maria Catalina Kovacs
There is no tracking on this site. No analytics, no cookies, nothing recording where you went or how long you stayed.
But I would like to know if something here was worth your time. If it was, press the heart. It tells me nothing about you. It is a number going up, and it is what tells me to keep writing.
If you want more of this, I am on X. @Queendemetriak