The constraint that shaped everything: publish now, serve fast
olshtyn.com is a city portal for Olsztyn, written for the people who live there and the people planning a visit. The build ran in 2012, took about six weeks, and included the visual identity alongside the site itself.
The defining constraint was not the design and not the feature list. It was a conflict between two requirements that a news site holds at the same time. An editor publishing a story about something happening in town needs it visible immediately, with no delay at all. The same moment is when traffic peaks, which is exactly when serving every request through PHP and the database is least affordable. Every other decision in this project follows from holding both of those at once.
The usual place in a case study for a table of results is empty here on purpose. Audience numbers belong to the publisher, not to a portfolio entry on our site. What can be told honestly is what was decided and why, and that is the part which transfers to the next build.
How caching was made compatible with breaking news
The stack is WordPress on PHP with MySQL, Redis and Memcached as cache layers, a CDN in front, and HTML5, CSS3, SASS and JavaScript on the browser side. Code is versioned in Git, and the front end talks to the back end over AJAX and REST endpoints.
A news portal has one large advantage over a shop when it comes to caching: almost every view is identical for every visitor. There is no basket, no per-user pricing, no session that leaks into the page. That means the overwhelming majority of page views can be served without ever starting PHP. The hard part is not caching, it is invalidation.
Time based expiry is the wrong tool here. Set it long and the editorial team cannot see their own article, so they start demanding the cache be switched off, usually during the busiest hour of the year. Set it short and the cache stops helping at precisely the moment it is needed. The cache is therefore invalidated by an event rather than by a clock: saving a post clears the front page, the relevant category listings and that post’s own URL, and leaves the rest of the site untouched. Publication becomes the trigger, and the editor sees the result immediately without anyone widening the blast radius to the whole site.
Redis and Memcached carry different jobs. The object cache shortens the repeated queries that every request pays for, such as menus, category listings and the heavier archive queries. Volatile data that does not need to survive a restart lives separately. Keeping them apart has an operational payoff: flushing the object cache during an incident does not log anyone out of the admin.
The CDN adds the third layer, and it matters most for the visitor who is not local. Someone reading from another city or from a phone on a roaming connection is served static assets from a nearby node, and the application server never sees those requests at all. On guide material, which is image heavy by nature, that is the single largest saving in the whole deployment.
Three content types instead of one stream
The layout and element placement came from the client. What sat on our side was translating it into templates, responsive behaviour and a content model the editorial team could maintain without a developer. The content model was the real work.
City portal material lives at three different speeds. A news item matters for a few days and then matters only to search engines. An event listing is valuable up to the day it happens and then inverts: it stops being an invitation and becomes an archive record. A guide to a landmark or a lakeside trail does not age at all, and it is the material that pulls search traffic for years. Default WordPress drops all three into one bucket sorted by date, which means the most durable content disappears from view fastest.
So there are three content types: news, an event carrying a date and a location, and evergreen guide material with no expiry. The consequence only becomes visible after a year of operation. A finished event drops off the front listings by itself, instead of hanging there until somebody remembers to remove it. Guide material stops competing for position with today’s news, so nobody has to fake a fresh publication date every month to keep it visible. The editor answers the question of what kind of material this is once, at creation, rather than policing its visibility every week.
Taxonomy was deliberately kept narrow. The tempting model describes everything along every axis at once: district, topic, event, person, venue. It looks flexible on launch day and collapses in month six, because two editors classify the same article differently and the archive stops filtering usefully. The category set is short and closed; anything genuinely fluid lives in free tags that nothing in the navigation depends on.
URLs were treated as a long term asset. A post URL carries the readable title, with no date and no numeric id, and the category is not part of the path. Categories get reorganised eventually, and changing the URL of an article published a year earlier costs its ranking and requires a redirect. A URL that does not depend on editorial housekeeping survives a reorganisation for free.
Asynchronous loading and the costs nobody sees
News listings load further items asynchronously through AJAX and dedicated REST endpoints rather than reloading the page. The convenience is obvious. There are two costs, and in 2012 both were easy to miss.
The first is discoverability. Content that only appears after a click can be invisible to a crawler. So the first batch of any listing is present in the page source, asynchronous loading handles only subsequent pages, and numbered pagination stays in the markup even when the interface does not show it. That pagination is the path for a crawler and for any visitor without JavaScript.
The second cost is caching. An endpoint returning a slice of a listing is a separate URL. Either it has its own cache policy or it becomes the hole through which peak traffic pours into PHP. Those endpoint responses are cached like pages and invalidated by the same publication event.
Maps and galleries follow the same reasoning. A third party map is a few hundred kilobytes of script plus a connection to somebody else’s server, paid whether or not the reader ever looks at the map. It loads when the visitor actually reaches the location section, and until then a static image with the address stands in its place. For a reader standing on a street checking one fact on a phone, that difference decides whether the page opens at all.
Images deserve their own note, because on a city portal they are most of the bytes. Editors upload straight from a camera, at sizes many times larger than any view on the site. Processing at upload time produces variants for the listing, the article view and the gallery, and the template tells the browser which variant to use at which screen width. Without that, mobile readers download desktop sized photographs, pay for it in data and time, and the editorial team never finds out, because on their connection everything is fast.
The project also covered the visual identity, and the mark was drawn for the place it actually lives. A news site logo spends most of its life in a header bar a few dozen pixels tall, on a phone, next to a menu button, and it has to stay recognisable as a browser tab icon too. That reverses the usual priorities of a print mark: legibility at small scale, behaviour on a flat background, and a simplified variant that does not fall apart at a dozen pixels wide. A horizontal-only mark with fine detail presents beautifully in a deck and disappears in a real header, so the variants were produced alongside the templates rather than before them.
Migrating an archive that predates the site
Part of the material came from earlier versions of the service and had to be brought across with its history intact. Importing from archived snapshots, including Web Archive, looks like an hour of work and takes several days, always for the same reasons.
Older sites used different character encodings, so Polish diacritics come back from an import as byte soup, visible only when somebody actually reads that paragraph. Old URLs have a different shape from new ones, so every imported item needs a redirect from its former address, or the site throws away every inbound link and every ranking it built over years. Archived images are often incomplete, and their captions live inside the body text instead of in a caption field.
Order of operations mattered. Map old URLs to new ones first, import the content second, verify a manual sample on a copy of production last. The reverse order ends in a second import, because fixing URLs after go-live means some traffic has already landed on nothing.
Maintenance, and one rule about plugins
A news site never idles, so maintenance was planned alongside the build rather than after it: core, theme and plugin updates, log review, backups whose restore path is actually exercised rather than merely scheduled, and small functional changes driven by how the editorial team really works.
One rule is worth stating plainly. Security here is handled in code and in server configuration, not by adding another security plugin. A plugin of that kind runs inside PHP, which is to say after the request has already started the application, it frequently becomes a vulnerability of its own, and it taxes every request. On a portal with uneven traffic that has to survive a spike on the day something happens in town, that is a bad trade. Filtering at the edge, capability checks in code and a hardened server configuration cost less and act earlier.
Removing a comment about a municipal matter is an editorial decision rather than a technical one, so moderation was handed to people and not to a filter. The editorial team got moderation tools and a queue for held comments. Discussion under local news is the liveliest part of a city portal and the part that most often needs someone to read it properly. An automatic filter would have been cheaper to run and wrong in exactly the cases that matter, which are the ones where a resident is accusing the council of something.
Pre-launch testing ran against a copy of production rather than an empty install, and the reason is that an empty install has ten posts, so every listing fits on one screen, every query is instant and there is no archive at all. A portal’s problems start at a few thousand items, when category pages slow down, internal search returns uselessly ordered results and pagination reaches page twenty, which nobody opened during testing, while a production copy shows all of that within minutes.
Summary and what the analysis phase settled
The next portal starts from the question of who reads it and how often, and only then from what goes in the database. That order is the part of this build worth carrying anywhere else.
Here the first question had two answers rather than one. Resident and visitor want different things from the same front page, and the analysis phase accepted that openly instead of averaging them into a single imagined reader. Scope followed from it and covered content management, an events calendar, location maps, galleries and comments. The reference points named by the client were portals with a strong community focus, which is why comments and events ranked high while decoration ranked low.
The schedule was split into stages so the editorial team could start filling the site before the last work was finished. For a future build the technical layer carries over, and so does the way caching is reasoned about for editorial content. The content model does not, because it was written for one city, one editorial team and one publishing rhythm.
Frequently asked questions
Practical answers to apply the topic in real execution.
What scope did the olshtyn.com project cover?
#How did delivery run for olshtyn.com?
#What was the hardest technical part of olshtyn.com?
#What part of olshtyn.com could be reused on another build?
#Need an FAQ tailored to your industry and market? We can build one aligned with your business goals.
Let’s discuss