Technology
Can You Schedule Posts on a Static Site? Four Mechanisms and What Each Guarantees
A prebuilt site is frozen between builds, so a future date is a filter rather than a schedule. Four mechanisms, what each one actually guarantees, and the consequences nobody documents.

A content system that shows a date picker and the word Schedule has made a promise on behalf of the whole pipeline. On a prebuilt site that promise is considerably harder to keep than the interface suggests, because the thing that publishes the post is not the date. It is whatever causes the next build.
A prebuilt site is frozen between builds
A prebuilt site is a set of files produced once and served unchanged until they are produced again. When the build runs, some piece of code reads each entry, compares its publication date with the current moment, and either includes the entry in the output or leaves it out. That comparison happens exactly once per build. Between builds nothing on the server is watching a clock, nothing is waiting for a date to arrive, and nothing will change of its own accord.
This is the whole difficulty in one sentence. A future date on a prebuilt site is a filter, not a schedule. It describes a condition under which the entry would be included, and it is evaluated only when something asks. If nothing asks between Tuesday and Friday, an entry dated Wednesday appears on Friday, and it appears with Wednesday printed on it, which is worse than appearing late without explanation.
A future date on a prebuilt site is a filter, not a schedule. Something else has to supply the schedule.
The exception, and it is the only one, is a system that moves the comparison out of the build and into the request. That is a genuinely different architecture with genuinely different costs, and it is the fourth mechanism below. For the other three, the honest framing is that the date field supplies the condition and some external mechanism supplies the timing. Choosing a scheduling design is really choosing which mechanism supplies the timing, and every property an editor cares about, from precision to reliability to cost to what happens when something breaks, follows from that choice rather than from the date field.
Four mechanisms, and what each one guarantees
There are four in common use for scheduled publishing on a static site. They are not interchangeable, they do not cost the same, and, most importantly, they do not offer the same guarantee. Read each one as an answer to a single question: if an editor sets a post to go live at a particular moment, what can you truthfully promise them?
Build-time filtering with periodic rebuilds
The site rebuilds on a fixed interval regardless of whether anything has changed, and each build applies the filter, so future dated posts fall out of the output until the first build that runs after their moment has passed. This is the simplest mechanism to reason about and the hardest to break, because it holds no state. It does not need to remember that a post is pending. It does not need a queue. Every run recomputes the whole answer from the content, which means a missed run costs nothing but time, and the next run repairs it without anyone intervening.
What a cron rebuild guarantees is bounded and easy to state: publication no earlier than the stated moment, and no later than the stated moment plus one interval. That is a real guarantee, and it is the only one of the four that is self-healing by construction. What it costs is builds that mostly do nothing. A site rebuilding hourly runs twenty-four builds a day to publish, on most days, nothing at all, and every one of those builds consumes compute and, on a large site, minutes rather than seconds. The interval is therefore a direct trade between precision and expenditure, and on a site with a long build the trade gets ugly quickly: an hourly rebuild that takes eight minutes spends an eighth of its existence rebuilding a site nobody changed.
An external trigger firing a deployment
Something outside the site holds a timer, and at the scheduled moment it calls a deployment hook. The build runs, the filter now includes the entry, and the post appears. Granularity is as fine as the timer, which in practice means it is as fine as you want it to be. It is also much cheaper in aggregate than periodic rebuilding for a site that publishes occasionally, because it runs one build per scheduled item rather than one build per interval forever.
The guarantee is weaker than it looks, in two specific ways. First, it is not self-healing. Periodic rebuilding forgets and recovers; a one-shot trigger that does not fire has simply not fired, and unless something reconciles pending items against reality the post waits indefinitely for a human to notice. Second, the trigger fires at the moment but the post appears at the moment plus the build duration. On a site that builds in twenty seconds nobody cares. On a site that builds in six minutes, an editor who was promised a precise moment gets a post six minutes late, every time, and will eventually ask why. Firing the trigger early to compensate is possible but fragile, because build duration varies with content volume and with whatever else the pipeline is doing that day, and firing too early means the filter excludes the entry and the build publishes nothing at all.
On-demand revalidation
Rather than rebuilding the site, the trigger invalidates and regenerates only the pages affected by the change. The timing story is identical to the previous mechanism, because it depends on the same external timer and inherits the same lack of self-healing, but the latency between trigger and visibility collapses, and the cost is per page rather than per site. For a large site publishing frequently, this is usually the right shape.
It introduces a failure mode the other mechanisms do not have, and it is a quiet one. The set of pages affected by publishing a post is much larger than the post. The topic listing changes. Pagination changes, and on a paginated archive publishing a single item shifts the contents of every page after the first. The feed changes. The sitemap changes. The home page changes if it shows anything recent. Related-article modules on other posts may change. If the invalidation set is written by hand and covers only the article URL, the outcome is an article that is live, correct, and reachable by nothing at all, which is indistinguishable from not having published it and considerably harder to diagnose. Derive the invalidation set from the content graph rather than from a list somebody maintained once.
Gating at the edge, at request time
The post is built and deployed immediately, and the decision about whether to serve it is made per request by comparing the current moment against the entry's date. Granularity is exact, because there is no interval and no trigger. The condition is evaluated by the same code that answers the request. No build has to happen at the scheduled moment at all, which removes the entire class of problems the other three share.
Two costs, and the second is the one to say out loud. The first is that a route which was static is now dynamic, or is static with a cache lifetime short enough to notice the change, which quietly reintroduces the interval you adopted this mechanism to escape. Full-page caching and request-time gating pull against each other. You can have the precision or the cacheability of that route, and working out which one you actually need is the whole design decision.
The second cost is that the content is present in the deployed output before its moment. The gate controls serving, not existence. Anyone who can reach the origin directly, anyone with access to the deployment, and anything that reads the build output rather than the served response can see an embargoed post before its publish date. That is acceptable for an ordinary editorial schedule and unacceptable for a genuine embargo, and the two requirements are routinely confused because the interface calls both of them scheduling. If the reason for the future date is that the content must not exist publicly yet, this mechanism does not meet the requirement, and no amount of correctness in the gate changes that.
The related question of which unpublished states are reachable at all, and by which crawlers, is treated separately in where drafts live and what stops them, and is deliberately not repeated here.
- Periodic rebuild: precision equals the interval, self-healing, cost is constant and paid whether or not anything is published.
- External trigger: precision equals the timer minus the build duration, not self-healing, cost is one build per scheduled item.
- On-demand revalidation: precision equals the timer, not self-healing, cost is per affected page, and the affected set is always larger than the post.
- Request-time gating: precision is exact, nothing to fire and nothing to miss, but the content exists in the deployment before its moment and the route stops being cheaply cacheable.
Granularity, and the gap between the promise and the guarantee
The precision of the date field is a decision made in the interface. The precision of the system is a property of the trigger. These two numbers have no relationship to each other unless somebody deliberately makes one, and by default they diverge in the direction that embarrasses you: a picker offering minutes, sitting on top of a mechanism that rebuilds every six hours.
There are two honest repairs. The first is to coarsen the picker until it matches the mechanism. Offer six-hourly slots if you rebuild six-hourly, and the interface stops lying. The second is to keep the fine-grained field and state the guarantee next to it, in the editor's own words rather than in engineering language: this post will not appear before nine in the morning, and will appear by three in the afternoon at the latest.
The second repair is better, and the reason is worth spelling out because the first is more tempting. The date is not only an instruction to the publishing mechanism. It is also the publication date printed on the article, the sort key for the archive, the value the feed carries, and the thing a reader sees. Coarsening it to fit the trigger destroys information that has nothing to do with the trigger: the order of two posts scheduled within the same window, and the accuracy of the date the reader is shown. It also bakes today's infrastructure into content that will outlive it, so that improving the mechanism later leaves you with a body of posts whose dates were rounded for a reason that has since disappeared.
The counter-argument deserves a hearing, because it is not weak. If the interface accepts a precision it cannot honour, some editors will believe it regardless of the caption, and a caption is a weaker safeguard than a constraint. That is true, and it is the strongest case for coarsening. What would have to be true for the coarsening position to win outright is that editors read constraints and ignore captions, which is a claim about your particular editors rather than about editors in general, and one you can check by asking them what they think the date does. If they answer with an interval, the caption is working. If they answer with a moment, coarsen the field and accept the loss.
Timezones, and the ambiguity inside every scheduled date
Every scheduled date contains three timezones, and a system is correct only when it has decided, explicitly, what to do about each. There is the zone the editor was thinking in when they chose the time. There is the zone the value was stored in, if any. And there is the zone the build machine runs in, which is very often UTC and almost never the editor's.
The dangerous case is a value stored without an offset, because a naive datetime is not a moment. It is a wall-clock reading that means different instants in different places, and the comparison at build time will reinterpret it silently, in the build machine's zone, with no error and no warning. An editor working in India Standard Time who schedules a post for nine in the morning, whose value is stored as a bare string, and whose build runs in UTC, gets a post that becomes visible in the middle of their afternoon. Nothing has malfunctioned. Every component did exactly what it was told to do with the information it was given.
The rule that follows is short. Store an absolute instant with its offset. Display it in the editor's zone and name that zone in the interface, so the editor can see which nine in the morning they picked. Never store a local wall-clock time and hope the build agrees about where it is standing.
Daylight saving adds a second problem that only shows itself twice a year. A one-off publication is an instant and is unaffected. A recurring rule, such as every Monday morning, is not an instant, and storing it as one means it drifts by an hour across the boundary, while storing it as a bare wall-clock rule without a zone means it jumps. The workable arrangement is to store the rule together with the zone it was authored in, and resolve it to a concrete instant at the point the trigger is scheduled rather than at the point the rule was written.
The third detail is the comparison itself. Inclusion is decided by testing the current time against the publication instant, and the current time comes from whichever machine happens to be running. Where the trigger and the moment are aligned to the second, a build that starts a fraction early evaluates the comparison as false and publishes nothing, and the next thing anyone sees is a successful build with the post still missing. Fire the trigger a little after the moment rather than exactly on it, or allow a small grace in the comparison. It is a one-line concern that produces a genuinely confusing failure, precisely because the build reports success.
Downstream consequences once the moment has passed
Getting the post onto the page at the right time is the part everyone designs for. The problems that survive into production are almost all in what is derived from the post, because scheduling changes when those derivations happen and separates them from the moment a human was paying attention.
Lastmod accuracy
Google Search Central documents the sitemap lastmod value as a signal it uses only where the value is consistently accurate, and describes it as reflecting the last significant modification of the page. Scheduling breaks this in both possible directions. Setting lastmod to the build time makes every URL on the site appear to have changed on every rebuild, which under a periodic mechanism means the whole site claims to have been edited hourly, and a signal that fires constantly carries no information. Setting it to the creation date and never touching it again means genuine edits are invisible, which is the opposite error with the same practical result.
The correct value for a scheduled post is its publication instant: not the moment the draft was written, and not the moment the build ran. Derive lastmod from the content's own record of substantive change, and make sure the derivation survives the scheduling path specifically, because that is exactly the path where the created date, the published date and the build date are three different values sitting close enough together to be confused.
Duplicate feed entries
A feed reader decides whether it has seen an item before by its identifier, not by its title and not by its position in the file. If that identifier changes, the item is new again, and every subscriber is shown the same article twice. Scheduled publication makes this failure considerably more likely than manual publication does, because publication is often the exact moment at which the final URL is fixed, the slug is settled, or the date component of the path is filled in, and identifiers derived from any of those change at the worst possible time.
The rule is to assign a stable identifier when the entry is created and never derive it from anything that can change afterwards. Not the URL, not the date, not a content hash, and not a build artefact. An identifier that is permitted to change is not an identifier, it is a description, and the difference only becomes visible to you after it has become visible to your subscribers.
Cache invalidation
Publishing one post is a change to a set of pages, and the set does not stop at the ones you generate. Beyond the article there is the archive, the topic listing, every page of the pagination, the feed, the sitemap and the home page. Beyond those there are caches you do not control: a content delivery network holding the listing page, an intermediary proxy, and a reader's browser sitting on whatever lifetime you set the last time you thought about it.
The consequence is specific rather than general. If the listing page was served with a long lifetime, the post is live at its own address and absent from every route a reader would use to find it, for as long as that lifetime lasts. Either the invalidation set is derived from the content graph, so that publishing anything expires everything that references it, or the listing surfaces carry a lifetime short enough that the gap is tolerable. Choosing neither produces a site that publishes exactly on time and appears, to everyone looking at it, not to have published at all.
Silent build failure, and why nobody notices
This section is a structural claim rather than a story, and it holds for any unattended trigger regardless of how it is built. When a person publishes by pressing a button, they are watching. A failure produces a complaint within seconds, from someone who knows exactly what they just did and what was supposed to happen next. Scheduled publication removes that observer deliberately, since removing them is the entire point of scheduling, and in doing so it guarantees that the failure of a scheduled build is unobserved by design, until somebody happens to look at a page they had no particular reason to look at.
The second structural point is that the failure need have nothing to do with the scheduled content. A build is all or nothing: it either produces a deployable site or it does not. A dependency that stopped resolving, a link check that started failing on an unrelated page, a lint rule that tightened, an expired credential on the deployment hook. Each of these fails the build, and the scheduled post does not appear, even though the post itself was correct and would have published without complaint. The mechanism you chose determines what happens next. A periodic rebuild will try again shortly and may well succeed if the cause was transient. A one-shot trigger has already spent its only attempt.
Retries make this worse before they make it better. A trigger that retries and eventually succeeds converts a visible failure into an invisible one, and conceals a pipeline that is failing systematically right up until the run where the retries are exhausted, at which point the problem is both urgent and old. The same is true of a periodic rebuild that fails intermittently: because it is self-healing, the symptom disappears before anyone investigates the cause.
Scheduling is therefore only as trustworthy as its alerting, and the alerting has three requirements. The trigger must report both that it fired and what the outcome was, because a trigger that reports only firing tells you nothing you needed to know. Something must verify that the expected content is actually present after the build, rather than that the build exited successfully, because a build can succeed perfectly while the filter excluded the item for a timezone reason and produced a site with no post in it. And the alert must reach a person who is responsible and awake, which is a scheduling problem of its own for anything published outside working hours, and one that tends to be discovered rather than designed.
What to say to the editor before you offer scheduling at all
The honest sentence is not complicated, and saying it costs far less than the support conversation that follows from not saying it. Your post will not appear before the moment you chose, and it will appear no later than one further trigger after that moment. Everything an editor needs is contained in that promise: it will not be early, it will not be much late, and the upper bound is a number you can actually give them.
Three further things belong in the interface rather than in the documentation, because documentation is read once and interfaces are read every time. What the upper bound actually is, in minutes or hours, next to the field where the date is chosen. What happens if the build fails, meaning whether the system will try again on its own, which depends entirely on which of the four mechanisms is underneath, and whether anybody will be told. And whether the content exists in the deployed site before its moment, because an editor scheduling a routine post does not care and an editor scheduling an embargoed announcement cares about that more than about anything else on the page.
Where this sits in a wider architecture depends on how content reaches the build in the first place, which is the subject of git-backed or API-backed. Disclosure: Zealsync develops Acrosite, a git-backed content publishing product, so this is a design question the company has had to reason through rather than merely observe from a distance. Nothing above describes how Acrosite or any other tool implements scheduling. It describes what the available mechanisms can and cannot promise, which holds regardless of the software in front of them.
Make the decision explicitly and early, because it is one of the few content-infrastructure choices that is difficult to reverse quietly. Editors calibrate to the guarantee they were given, plan launches around it, and tell other people times. Changing the mechanism later changes the promise, and the promise is the part they remember. If you are weighing the options for a publishing setup and want the reasoning applied to your own constraints rather than to a general case, get in touch.
Can you schedule posts on a static site?
Yes, but the word schedule promises something a frozen build cannot deliver on its own. A future date is a filter applied when the site is built, so something has to cause a build at the right moment for the date to mean anything. Four mechanisms supply that timing. A periodic rebuild guarantees publication within one interval of the moment and repairs itself if a run fails. An external trigger firing a deployment gives you the timer's precision minus the build duration, but does not recover from a fire it missed. On-demand revalidation is the same trigger with far lower latency and per-page cost. Request-time gating at the edge is exact, but the content exists in the deployment before its moment.
How precise can scheduled publishing be?
Precision is set by whatever triggers the build, never by the date field. A picker offering minutes on top of a six-hourly rebuild is precise in the interface and six-hourly in reality. A periodic rebuild gives precision no better than its interval. An external trigger or an on-demand revalidation is only as reliable as the thing firing it, and lands the build duration after the moment rather than on it. The honest promise to make to an editor is this, in full: the post will be published no earlier than the moment you chose, and no later than one further trigger after it. State that upper bound in real units, next to the date field, where it will be read.
Relevant Zealsync pages

