Skip to content
bigrobot.
E-commerce · D2C · 2025

D2C home furnishings brand

A new Next.js front end, a product feed built from scratch and one Performance Max campaign instead of seven — revenue up 312% year over year.

Scope of work
Custom e-commerceNext.js websitesGoogle Ads
Stack
Next.jsTypeScriptWooCommerceVercelGoogle Merchant CenterGoogle AdsGA4
+312%
revenue year over year
4.1×
ROAS in Performance Max
1.1 s
mobile LCP, 75th percentile
−41%
cost per order

Where we started

The client sells upholstered furniture, lighting and textiles directly to consumers through a single owned store. The WooCommerce install was five years old, ran 38 active plugins and used a premium theme where every template went through the same visual page builder. Mobile LCP on product pages sat between 5.8 and 6.4 seconds, and past roughly 300 concurrent sessions the server started returning 502s. In the second week of November the store went down for 40 minutes in the middle of an email campaign, which is when they called us.

The second half of the problem was in Google Ads. Three agencies had run the account in sequence, each adding its own structure and none cleaning up after the last: seven parallel Performance Max campaigns, four legacy shopping campaigns and two brand campaigns bidding against each other on the same term. Conversions were reported twice, once by a plugin and once by a tag on the thank-you page, so a reported 6.2 ROAS was in reality around 3.1. A daily budget of roughly EUR 1,100 was spread across campaigns with no relationship to the margin of the categories being sold.

The product feed came from a plugin that pushed 4,100 items to Merchant Center, of which 1,350 were disapproved or limited. Several causes stacked up: no GTINs on made-to-measure items, manufacturer-copied descriptions, dimensions written into titles instead of attributes, and colour variants submitted as separate products with no item_group_id. In practice less than half the catalogue was advertised, and the two highest-margin categories — modular sofas and pendant lighting — never entered an auction at all.

What we did

  1. 01

    Measurement first, code second

    For three weeks we collected data in parallel from server logs, GA4 and a raw order export straight out of the WooCommerce database. It turned out 22% of orders GA4 attributed to organic came from untagged email links, and the double conversion tracking was inflating ROAS by exactly 2× on remarketing-heavy campaigns. Before touching anything else we moved tracking to server-side GTM with one source of truth: a purchase event fired from the backend after payment confirmation. Another 21 days went to nothing but collecting clean data, and that was the hardest conversation of the project, because the client expected campaign work in week one.

  2. 02

    Headless: Next.js on the front, WooCommerce behind it

    We did not migrate the product or order database — WooCommerce stayed as the admin panel the operations team already knew. We built a new front end on the Next.js App Router that pulls data through the Store API and renders listings and product pages statically with 15-minute revalidation. Cart and checkout run on the new front end with the session synced through an HttpOnly cookie, which meant we never had to rebuild the payment gateway integration. The most expensive part was re-implementing oversized shipping logic: 14 price thresholds based on parcel volume and region, spread across three plugins, which had to be extracted and rewritten as a single module.

  3. 03

    A product feed written from scratch

    We dropped the plugin generator and built our own endpoint that assembles the feed directly from the database. Colour variants were tied together with item_group_id, dimensions moved into product_detail attributes, and titles got a fixed template of brand, type, material, dimension. For 2,600 items with no manufacturer codes we generated our own MPNs and set identifier_exists to false, which unblocked them in auctions within 48 hours. We only rewrote descriptions for the 340 fastest-moving SKUs — below about eight units a month the copywriting cost does not pay back, so the rest waited for the next quarter.

  4. 04

    Restarting the Ads account instead of optimising it

    We switched off every legacy campaign rather than fixing them: with broken measurement, the algorithm's learning history was worthless. Three structures were left — one Performance Max with asset groups split by gross margin, one brand search campaign with negatives, and one campaign optimised purely for new customer acquisition. For the first 18 days we held target ROAS at 2.5 so the system could gather conversions at all, then raised it in steps to 4.0. We knowingly gave up roughly EUR 14,000 of revenue during that learning window rather than bake the old, wrong conversion signals back in.

  5. 05

    Performance measured as a budget, not a Lighthouse score

    Instead of chasing a Lighthouse number we set one condition: LCP under 1.5 s at the 75th percentile of mobile field data in CrUX. Product images moved to next/image with AVIF and fixed dimensions, and the product gallery now prioritises only the first frame and loads the rest after interaction. Five marketing scripts went into Partytown and one was removed outright once we showed it cost 340 ms of TBT and produced 11 leads a month. We only reached 1.1 s on the third attempt — the first deployment stalled at 2.3 s because of a blocking currency rate fetch nobody had spotted in the audit.

Results after 11 months

We compare the full 11 months after the new front end went live against the same period a year earlier, on the same catalogue and with no change to discounting. Revenue grew 312%, and roughly a third of that growth came from categories that had never been advertised because their items were rejected in Merchant Center. ROAS settled at 4.1 with the daily budget raised from about EUR 1,100 to EUR 2,600, so scale grew faster than acquisition cost.

Not everything worked. Desktop conversion rate only improved 6% — practically all of the gain came from mobile, where we went from 0.9% to 2.4%. Returns held steady at 14% even though we expected a drop after adding dimensions and material composition to product pages; the post-purchase survey showed the dominant reason is colour differing from the photo, not missing specifications. That finding moved the next phase of work away from interface changes and towards product photography and colour profiles.

  • +312% revenue year over year on an unchanged catalogue
  • 4.1 ROAS against a real 3.1 before the account restart
  • Mobile LCP of 1.1 s (75th percentile field data) instead of 5.8–6.4 s
  • Mobile conversion rate from 0.9% to 2.4%
  • 3,940 of 4,100 items approved in Merchant Center, up from 2,750
  • 41% lower cost per order on a 133% larger budget
What surprised me most was being told to wait a month and leave the campaigns alone. That is hard to accept when you are looking at spend every day. In hindsight it is the one part of the project I would repeat exactly as it was.
Marta Zielińska · E-commerce Director

It starts with an audit

Tell us in two sentences what you want to achieve. We reply within one business day.