A new store on a live business
RMC Baltic is a distributor of genuine OE car parts, new and used, selling at the same time through its own online store, marketplaces and over the phone. The catalogue holds more than 25 thousand items and almost a quarter of a million photos, and every part is identified by the car manufacturer's part number.
The brief sounded simple: a new store. The conditions were harder. No data migration, not a single page address changed and no changes to the automations that the entire order handling runs on. The store was to change underneath and on the surface, and the company's back office was not supposed to notice.
The audit: the store was selling, but measuring nothing
We started with an audit of the live site, without access to the admin panel. It produced 28 findings in six areas: 6 critical, 14 high, 8 medium. Each one backed by a number, not an opinion.
The store had neither Google Analytics 4 nor Tag Manager, and the only tag was Google Ads conversions. There was no cookie banner either, so the Consent Mode declaration inserted automatically by the Google plugin was permanently stuck on "denied". The company knew nothing about baskets, order values or sales sources, and the ads were being settled against a model rather than real transactions.
The rest of the list was just as specific. The home page and category pages took around five seconds to generate. A menu with several hundred links sat in the code of every page. On the checkout page customers had 517 links leading away from the purchase and the full terms and conditions pasted under the form. The OE number was merged with the warehouse code in a single field. The privacy policy described tools the store did not use, and the terms based complaints on repealed regulations.
The audit also showed what worked well, and that mattered for the decision. The store was not built on a page builder, product pages responded quickly and the layout did not jump. The foundation was worth keeping. What needed replacing was the layer above it.
Home page generated in 4.5-4.9 s
No GA4, no cookie banner, Consent Mode blocked
517 links on the checkout page
Accessibility 80 out of 100, zoom disabled on phones
OE number merged with the warehouse code
No security headers on the server
Legal documents out of line with the store as it was
Home page at 0.21 s to first byte
GA4 with ecommerce events, zero cookies before consent
19 links on the checkout page
Accessibility 100 on four key screens
Numbers separated, search that normalises how they are written
Five security headers, software versions hidden
New drafts of the legal documents
Agreements first, code second
Before the proposal was written, we built a clickable mockup on 24 real products from the client's catalogue, with real part numbers, prices and photos. The client's sales team prepared 46 questions about how the new store should work. We answered each one separately, and the document with the answers is one of the documents binding the project.
We closed the scope in nineteen items, from the home page to the test environment. Every feature in the code refers to a specific scope item, audit finding or mockup screen. At acceptance there was no discussion about what "was supposed to be there". There was a document with the status and the evidence next to every item.
Groundwork on the existing store
Object caching and security headers switched on in a night-time maintenance window, still on the old theme. Cookie consent and measurement as a module independent of the theme
New theme
A classic PHP theme, with no build process and no closed dependencies. Home page, catalogue with filters, product page, basket, checkout, customer account
Product data and part number index
OE numbers, warehouse codes and EANs separated, a custom search index including superseding numbers, data for Google
Industry modules
A separate plugin: returns without logging in, complaints with numbering and statuses, part-to-vehicle fitment, VIN-based enquiries
Integration and switchover
A copy of production, an order field contract, four gates, switching the theme on the live store and monitoring after launch
216 rules that could not be touched
At the heart of the client's operations is BaseLinker, a Polish multichannel order management platform. It pulls orders from the store every minute, issues invoices through Fakturownia (a Polish online invoicing service), books shipments with couriers, prints order sheets and labels, and sends emails and text messages. For BaseLinker the store is just one of several sources.
We compiled the list of automations ourselves, from the panel, in read-only mode. It came to 216 rules in 40 groups, 179 of them active. Fifteen apply directly to orders from the store, and 180 run for all channels with no source condition, so they cover the store as well.
The conditions in these rules read data coming from the store: the exact names of the delivery and payment methods, the NIP (Polish tax ID), the "I want an invoice" flag, the parcel locker ID and the buyer's comment. All it takes is for the new checkout to save the tax ID under a different key or change one letter in a payment name, and the invoice is not issued, the shipment is not booked and a cash-on-delivery order goes down the prepayment path. Nobody gets an error message. The rule simply does not fire.
We compared 200 orders from 75 days: what the store saved against what BaseLinker received. That gave us a contract of seven fields the rules depend on. The new checkout saves exactly the same keys, and the comparison tool returns one of two verdicts: MATCH or STOP. On a deliberately broken tax ID and payment name it returns STOP. Without a MATCH verdict, production was not changed.
Testing without risk to real orders
Every test order let into the production BaseLinker would have set off an avalanche: an invoice in the accounting system, a text message to a customer, a printed label, a job for a courier. So we adopted a one-way rule. The test environment only reads from the client's account and has nothing to write with.
- The import script rejects any API method other than reads and stops before anything is sent
- A test environment with no integration plugin, no REST keys and no webhooks, checked before every code review
- The access key kept outside the repository and out of the server's process list
- Personal data from orders never reaches the test server, the import pulls the catalogue only
- A copy of production before the switch: cut off from the internet, anonymised with verification, mail routed to a test inbox
We rebuilt the test catalogue from BaseLinker rather than from the store database, because that is where the client maintains the data. A full photo import meant almost a quarter of a million files. We stopped the first attempt, because the files at original size would not have fitted on the disk. The second downloaded them already resized, with four processes, over roughly fourteen hours.
A switchover through four gates
The new theme went onto the same installation, the same database and the same addresses. The old theme stayed on the server untouched, in a separate directory, as a way back with a single switch. Before anything changed on production, the copy of production had to pass four gates.
Order field contract
The same reference orders on the old and new theme: courier, cash on delivery, parcel locker, invoice, collection in person, abroad, card payment, euro, coupon. Verdict: MATCH
Parcel lockers
The map button and the hidden pickup point fields present in the new checkout, because the rules that book shipments depend on them
Payment gateway
An order from the Polish and the English version reaches the payment provider exactly as it did on the old theme
Appearance on real data
1,311 addresses on the copy checked for server responses, a PHP log free of errors, a manual review of pages, product pages and checkout
The gates were not a formality. The fourth caught empty pages built with the old theme's page builder, links to pages that did not exist and an error on a product page without vehicle data. A dress rehearsal the day before, in four runs, caught four more things, each of which would have got in the way during the actual switchover.
The switch itself took a few minutes: an on-demand backup, uploading two directories, activating the plugin and the theme, rebuilding the part number index. Then a readiness test on production (52 checks, 0 errors) and a control order that arrived in BaseLinker with the correct status, tax ID, company name, delivery and payment method.
A search that understands part numbers
In a parts store the customer arrives with a number copied from the part, from an invoice or from a catalogue. Everyone writes it differently. The new search normalises the notation: the same product is found by its number with spaces, hyphens or dots, with or without the manufacturer's trailing suffix.
- A dedicated part number index table instead of searching product fields, which was too slow with 25 thousand items
- Superseding numbers, predecessors and successors of a part in the same index
- Splitting numbers merged in one field with warehouse codes and symbols
- The manufacturer's number as MPN in the data for Google, on every product page with an OE number
Matching a part to a vehicle on model and year alone is, in this industry, a straight road to a return. So we set a hard rule: until the store has fitment data, every product page says "compatibility needs to be verified by VIN" and offers an enquiry form. At acceptance we checked 20 product pages: 20 times verification, 0 times "fits".
Shipping rates read down to the last zloty
The client did not want to describe once again things that already worked in their systems, and they were right. We read the shipping, payment and currency configuration from the panel ourselves and reproduced it in the new store. Costs are calculated by our own shipping method based on parcel weight, consistent with the existing rate card.
The code review after the rates were reproduced found four bugs at once, each changing the delivery charge. The most interesting one: in the existing rate card, a category rule with an empty cost does not add zero, it excludes the delivery method. A bumper cannot go to a parcel locker, and the old store enforced that even though it was written down nowhere. The new one enforces it too, with a warning in the admin panel when a rule points to a category that does not exist.
- Payment matched to delivery: with cash on delivery you cannot pay upfront, and vice versa
- Delivery and payment method names identical to before, because the BaseLinker rules recognise them
- The price list on the "Delivery" page generated from the shipping settings, not retyped by hand
- Delivery cost shown already on the product page, calculated with the same methods as at checkout
Measurement and consent done honestly
The cookie banner works as a separate module, independent of the theme. Denied by default, analytics and marketing start only after acceptance, and the "accept" and "reject" buttons carry equal weight. The banner copy reflects the requirements of the Polish Electronic Communications Law and GDPR, and the drafts of the new privacy and cookie policies are awaiting approval by the client's lawyer.
We deployed the Tag Manager container and the Analytics 4 configuration through the API rather than by clicking in the panel, so they live in the repository and can be recreated: 29 tags, key events from purchase to a phone click and a VIN enquiry, data retention and audiences. The check on production: before consent, zero analytics and advertising cookies and zero requests to Google and Meta. Orders reach GA4 with values that match BaseLinker.
Performance without a rebuild
The audit pointed to pages taking five seconds to generate. We expected the fix to require rebuilding the home page. In a night-time maintenance window, measuring after every change, it turned out that object caching was enough. It was included in the client's hosting plan, just never used.
One change in the window slowed the store down by more than a second, because the caching plugin was not yet connected to its server. We rolled it back after six minutes, following the rule: at the first symptom we roll back and look for the cause outside the window. Services that turned out in the hosting panel to be paid add-ons rather than switches included in the plan were put on hold. We had consent for maintenance work, not for making purchases on the client's behalf.
A purchase in the new store can be completed using the keyboard alone, which a dedicated test verifies. After the switch, a mobile audit across 396 screenshots found, among other things, horizontal scrolling at checkout and a form protection badge covering the buy button. The fixes went to production the same day.
The first 48 hours
From the evening before the switch a store watchdog was running: every few minutes it measured the response time of the home page, catalogue, product page and basket, regularly walked from basket to checkout and compared every new order with what BaseLinker had received.
A few hours after launch the client's hosting stopped responding for about two hours. The cause lay in the provider's infrastructure, but we found two things on our side. The cache had no graceful mode, so when its server restarted every request ended in an error instead of simply running slower. And BaseLinker only asks the store for orders from the last three hours, and after the outage it stopped polling the store. When the integration was restarted, an order placed that evening was already outside that window and was not picked up. We added a graceful mode and a module that extends the window to 72 hours for BaseLinker requests. The order came in on its own, and since then any interruption of up to three days catches up without anyone having to step in.
Eleven code reviews
Every larger stage ended with a code review, with a list of findings and a decision on each: implemented, rejected with a reason or deferred. There were eleven reviews, from the theme foundation to the fixes from the acceptance audit. One of them caught a test backdoor that would have been open to anyone on production, because the client's server identified itself as a local environment.
Once the client opened the test environment after being told the fixes were live, and found empty pages, no logo and no favicon. The tests checked features, not what a person sees in the first minute. Since then, before every demo we run a readiness test: every page from the menu and footer, the logo, the favicon, the server response. The same test later became one of the switchover gates.
Technology
WooCommerce
PHP 8.5
Classic theme with no build process
Custom industry modules plugin
BaseLinker
Fakturownia
InPost parcel lockers
Redis
Google Tag Manager
GA4 with Consent Mode v2
Google Merchant Center
Schema.org JSON-LD
hreflang
Docker
Playwright
Lighthouse
Results
What this project teaches
In a large company an online store is rarely a standalone system. It is the entry point to a chain in which an order turns into an invoice, a shipment, a printout in the warehouse and a message to the customer. The hardest part of replacing a store lies outside the store, in the automations that read its data and raise no errors when that data changes.
That is why we did not start by asking what the store should look like, but what must not break. A written list of rules, an order field contract, a copy of production, gates with an unambiguous verdict and a watchdog after launch are not bureaucracy. They are the only way to replace the store of a company that keeps selling as usual the whole time.