Two promises that pull against each other
Every catalogue site makes two promises at once, and on a lighting shop they contradict each other more sharply than almost anywhere else. The first promise is speed: a page should arrive from a cache, without waking PHP or touching the database. The second promise is accuracy: the availability shown next to a product should be true right now, and availability changes outside the website entirely.
Hold the page in cache for an hour and for an hour it can promise stock that no longer exists. Refuse to cache it and every single visit pays the full cost of building the page from scratch. led-lumina.pl was delivered in 2012, the build ran about six weeks, and this tension is what most of those weeks went into resolving.
The resolution was to stop treating the product page as one object. Descriptions, photographs, technical parameters and supporting copy are stable for weeks and can sit in cache aggressively. Stock level and availability are fetched separately, after the page has already rendered, and they alone decide whether the order button is live. The document the browser receives is identical for everyone, and the only time-sensitive element on it is clearly fenced off.
What a lighting catalogue actually is
Strip away the photography and a lighting catalogue is a table of numbers. Luminous flux, colour temperature, colour rendering index, beam angle, ingress protection rating, energy class, whether the fitting dims, whether a driver is included. Two products in this catalogue differ by those figures and by very little else.
That has a consequence for the content model that has to be settled on day one. A product description cannot be a text field into which somebody types the specification as prose. It has to be a set of separate attributes, because only then can you filter on them, compare across them and build lists of interchangeable alternatives. Prose is unsortable. Once the figures live inside sentences, the only way to answer “show me every neutral-white fitting above a given output” is to read every sentence.
So numeric attributes are stored as numbers and can be narrowed by range. Enumerated properties such as light colour or protection rating are taxonomies, so one label attaches to many products and can be corrected in one place later. The distinction looks administrative. It decides whether a filter by wattage range is a database query or a text search, which is the difference between a catalogue that scales and one that does not.
Variants, and the duplicate-page trap
The same fitting exists in several colour temperatures and several outputs. To a customer those are versions of one thing. To a warehouse they are separate lines with separate stock. Both views are correct, and the content model has to serve both without picking a side.
Give every variant its own post and the product page multiplies into a dozen near-identical documents, each with the same photographs and nearly the same text. A search engine looking at that sees a dozen near-duplicates and picks one to show, and the choice is not yours. Give variants no representation at all and you cannot display stock, which returns us to the first section.
What worked here was one parent entry carrying the description, the gallery and the shared copy, with child variants carrying only parameters and stock. One indexable page, accurate availability, and a single place to edit the words.
The integration, and what happens when it stops answering
Stock and catalogue changes arrive over an API from an external system, so that nobody retypes them. On an architecture diagram that is one arrow. In operation it is the hardest part of the build, because the external system is the one component whose behaviour you do not control.
Redis sits between the site and that system. Its job here is less about shaving milliseconds off a query and more about making the site survive somebody else’s outage. When the source stops answering, the intermediate layer serves the last known response rather than an empty section or an error. A visitor sees a page that may be slightly behind, not a page that looks broken, and the difference between those two is the difference between a lost sale and a delayed one.
Refresh intervals are set per data type rather than globally. Catalogue structure and technical parameters change rarely and can come through in a bulk pass. Stock changes within hours and travels by a separate lightweight channel that touches only the availability field. Splitting the two is cheaper than making one large job faster, because it reduces not the runtime but the amount of work that needs doing at all.
There is one more rule in this area that only becomes obvious after a few months of operation. A full import must never overwrite work done on the website side. Marketing copy, room-set photography, related-product links and search-oriented text do not come from a warehouse system, and if the import does not explicitly leave them alone, it will eventually eat them. That has to be decided in code, because it will be decided one way or another.
Images are the weight
A light fitting sells on its photograph, and photographs of light fittings are both difficult to shoot and expensive to transmit. They usually come in several views, often against a dark background, sometimes in a room setting. A catalogue of several hundred items is therefore an image library with a website attached, and the markup is a rounding error next to it.
Performance work went where the mass was. Files are processed on upload into the set of sizes that actually appear on the site: a thumbnail for listings, a medium view for the product card, full resolution for the zoom. A listing thumbnail is never the zoom image scaled down in the browser, because scaling in the browser saves pixels and not bytes, and bytes are what the visitor waits for.
A CDN handles distribution. Images never change once uploaded, so they can live in edge caches for a very long time and a second visit to a listing page does not reach the origin server at all. The AWS side carries what cannot be served from an edge: source file storage, backups, and the processing operations that are expensive but happen once.
Filtering without losing the address bar
The interface fetches results asynchronously so that changing a filter does not reload the page. That is a straightforward improvement and it hides a trap that caught a lot of catalogues from this period.
If a filtered result set has no address of its own, it cannot be bookmarked, cannot be sent to a colleague, and cannot be indexed. The filtering layer then works directly against the visibility the rest of the project is trying to build. So the state of the filters is reflected in the URL even though no page reload takes place, and arriving at that URL cold produces the same result set as clicking to it.
The markup side matters too. Filters have to be operable from the keyboard, and a specification table needs its headers bound to its cells. Without that binding a screen reader reads the specification as a run of unlabelled numbers, and the specification is the main content of this page, not decoration around it.
A search problem that only shops have
An editorial site writes its own text. A shop inherits it. Manufacturer descriptions are shipped to every reseller, so the same sentences stand on a dozen competing pages, and a search engine looking at a dozen near-identical documents picks one. The smallest site in that group rarely wins.
The answer lies in the layer the manufacturer does not supply. Category pages and filtered views carry their own introductory text, written against the question a buyer actually asks: how warm white differs from neutral white in a living space, when the ingress protection rating starts to matter, what it means when a specification says nothing about dimming. None of that exists in a manufacturer feed, because a manufacturer describes a product and a buyer is making a decision.
Structured data describes the product in a form a search engine can read, and semantic HTML5 organises the document so that the specification is a table rather than rows arranged to look like one. That second point is the accessibility point from the previous section arriving again from a different direction, which is usually a sign the decision was the right one.
Payments, shipping, and where the site’s responsibility stops
Payment gateways and courier systems introduce a class of failure that exists nowhere else in the build. Every other error can be fixed and the operation repeated. A payment happens once, its outcome arrives from outside, often late, and sometimes twice.
So order confirmation cannot rest on the customer arriving at a thank-you page. They arrive or they do not: they close the tab, they lose signal, they hit back. Order state is settled only by the notification that comes from the operator, and the handler for that notification has to tolerate being called more than once, because an operator in doubt will send it again. A handler that does not tolerate it creates a second order against a single payment.
Shipping draws a similar line. The site knows the weight and dimensions of what is in the basket, so it can quote a cost. It does not know the courier’s current capacity or restrictions, so it does not pretend to. Stating that boundary explicitly is more useful than a quote that is occasionally confidently wrong.
Security at the layer where it is cheap
Protection here is implemented in code and at the server rather than by installing a plugin that promises it. The reasoning is mechanical rather than ideological: a security plugin runs inside the same PHP process as the rest of the site, so it can only react once a request has already reached that process. Rate limiting and filtering at the edge stop the same request earlier and for less.
Inside the application what matters is what happens to input. Forms validate on the server, because browser validation is a convenience for the visitor and not a control. State-changing operations are protected against being triggered from another origin. Database queries are parameterised, so text a visitor typed never becomes part of query syntax.
Maintenance, and why it is different in a shop
Ongoing care covered core, theme and plugin updates, log review and regular backups. That list reads as routine, so it is worth saying how it differs from the same list on a brochure site.
An update to a catalogue wired into an external system is not a one-click operation, because a change in core can alter how the integration behaves, and nothing reveals that until the next synchronisation runs. Changes therefore went through a staging environment holding a copy of real data, not a clean install with a demo theme. A catalogue with hundreds of items and several integrations behaves nothing like an empty WordPress, and edge cases surface only against a realistic distribution of data.
Backups carry a second role that gets mentioned less often. In a shop the valuable artefact is not the files but the order database, so restoration has to be practised rather than declared. A backup nobody has ever restored is a hypothesis.
What carries forward
The method carries: separating stable data from fast-moving data, attributes as fields and taxonomies rather than prose, an intermediate layer between the site and any external system, and testing against a copy of production. Those decisions look the same in any catalogue regardless of trade.
The content model and the integrations do not carry. They were built around one client’s data and one brief in the Strony www category. A lighting attribute set means nothing outside lighting, and the shape of a data exchange depends on whatever stands on the other side of it. The next build starts with a scope analysis, and the quote follows that rather than preceding it.
Frequently asked questions
Practical answers to apply the topic in real execution.
What scope did the led-lumina.pl project cover?
#How did delivery run for led-lumina.pl?
#What was the hardest technical part of led-lumina.pl?
#What part of led-lumina.pl 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