Skip to main content

Code in the open

What I write for a client belongs to them and lives in their private repository. This is what I can show: two public repositories you can clone and run, three patterns I repeat on almost every B2B project, and the technical decisions behind this very site, with the numbers.

Public repositories

JavaScript · Shopify Functions

shopify-checkout-lab

The Shopify Function running in the Checkout Lab on the home page: the same file that ships to a store, with its input query and its tests. In the Lab you can change its configuration and watch what stops working.

Astro · JavaScript

portfolio

This site. Two languages without an i18n library, AVIF and WebP images generated with sharp before the build, and a content schema that fails the build when a project is missing a translation or a screenshot.

Three B2B patterns

None of this is client code: it is rewritten from scratch so it can be published. The problem each one solves is real, and it comes back on almost every wholesale project.

Full boxes at checkout

The problem

A wholesale buyer orders 7 units of something that leaves the warehouse in boxes of 6. Either somebody opens a box, or it gets sorted out on the phone two days later. A validation Function stops it at checkout and says exactly what to change.

The catch

The target also runs while the cart is being edited: without the early return, the buyer cannot type 7 on the way to 12. And there is a budget — 11 million instructions and a 128 kB input — so the query asks for the four fields the function actually reads.

src/cart_validations_generate_run.js
// Wholesale case packs. A buyer who orders 7 units of something that leaves the
// warehouse in boxes of 6 forces somebody to open a box, and that is found out
// after the order is paid. This blocks checkout instead, and says what to change.
//
// The case pack lives in a variant metafield (b2b.case_pack, integer). A variant
// without it is not sold by the box and is left alone.
//
// Target: cart.validations.generate.run (Cart and Checkout Validation API)

// @ts-check
/**
 * @typedef {import("../generated/api").CartValidationsGenerateRunInput} RunInput
 * @typedef {import("../generated/api").CartValidationsGenerateRunResult} RunResult
 * @typedef {import("../generated/api").ValidationError} ValidationError
 */

// Five lines is already a long list to read in a checkout banner.
const MAX_ERRORS = 5;

/**
 * @param {RunInput} input
 * @returns {RunResult}
 */
export function cartValidationsGenerateRun(input) {
  // The target also runs while the cart is being edited. Blocking there means the
  // buyer cannot type 7 on the way to 12, so only the checkout steps are checked.
  const { step } = input.buyerJourney;
  if (step !== 'CHECKOUT_INTERACTION' && step !== 'CHECKOUT_COMPLETION') {
    return { operations: [] };
  }

  /** @type {ValidationError[]} */
  const errors = [];

  for (const line of input.cart.lines) {
    if (line.merchandise.__typename !== 'ProductVariant') continue;

    const pack = Number.parseInt(line.merchandise.casePack?.value ?? '', 10);
    if (!Number.isInteger(pack) || pack < 2) continue;

    const remainder = line.quantity % pack;
    if (remainder === 0) continue;

    // Round up, never down: nobody wants to be told to buy less.
    const suggestion = line.quantity + (pack - remainder);
    errors.push({
      message: `${line.merchandise.title} ships in boxes of ${pack}. Change ${line.quantity} to ${suggestion}.`,
      target: '$.cart',
    });

    if (errors.length === MAX_ERRORS) break;
  }

  return errors.length ? { operations: [{ validationAdd: { errors } }] } : { operations: [] };
}
src/run.graphql
# The input query is half the work: a Function only sees what it asks for, and the
# input is capped at 128 kB, so an order pad with 200 lines cannot afford stray fields.
# `casePack` is an alias, so the metafield arrives under a name that reads as code.

query RunInput {
  buyerJourney {
    step
  }
  cart {
    lines {
      quantity
      merchandise {
        __typename
        ... on ProductVariant {
          title
          casePack: metafield(namespace: "b2b", key: "case_pack") {
            value
          }
        }
      }
    }
  }
}

Forty lines, one request

The problem

A B2B order pad with 40 SKUs and a quantity box on every row. One request per line makes the cart flash forty times, and the last response can land before the first. The Cart API takes an array, and it hands back the HTML of the sections that need repainting.

The catch

AbortController: the second click cancels the first instead of racing it. And when a variant is sold out, the 422 carries a readable description — showing it is cheaper than answering the email.

assets/quick-order.js
// B2B order pad. A buyer fills quantities across a table of 40 SKUs and adds the
// lot at once: one request instead of forty, the cart UI re-rendered from that same
// response, and a second click that cancels the first instead of racing it.
//
// <quick-order data-sections="cart-icon-bubble,cart-drawer">
//   <input type="number" data-variant-id="43210" min="0" step="6" value="0">
//   ...
//   <button type="submit">Add to cart</button>
//   <p role="status" aria-live="polite"></p>
// </quick-order>

class QuickOrder extends HTMLElement {
  #request = null;

  connectedCallback() {
    this.button = this.querySelector('[type="submit"]');
    this.status = this.querySelector('[role="status"]');

    // Inside a form, Enter in any quantity field has to work too, and listening to
    // both submit and click would send the batch twice.
    const form = this.querySelector('form');
    if (form) form.addEventListener('submit', this);
    else this.button?.addEventListener('click', this);
  }

  handleEvent(event) {
    event.preventDefault();
    this.add();
  }

  get lines() {
    return [...this.querySelectorAll('input[data-variant-id]')]
      .map((input) => ({ id: Number(input.dataset.variantId), quantity: Number(input.value) }))
      .filter((line) => line.quantity > 0);
  }

  async add() {
    const items = this.lines;
    if (items.length === 0) return;

    this.#request?.abort();
    this.#request = new AbortController();
    this.#busy(true);

    try {
      // Bundled section rendering: the cart markup comes back with the line items,
      // so the drawer and the bubble update without a second round trip.
      const response = await fetch(`${window.Shopify.routes.root}cart/add.js`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
        body: JSON.stringify({ items, sections: this.dataset.sections }),
        signal: this.#request.signal,
      });
      const body = await response.json();

      // A sold-out line answers 422 with a readable description: show it, don't swallow it.
      if (!response.ok) throw new Error(body.description || body.message);

      for (const [id, html] of Object.entries(body.sections ?? {})) {
        const target = document.getElementById(`shopify-section-${id}`);
        if (target) target.innerHTML = html;
      }

      this.dispatchEvent(new CustomEvent('cart:updated', { bubbles: true, detail: body }));
      this.#say(`${items.length} lines added`);
    } catch (error) {
      if (error.name === 'AbortError') return; // superseded by a newer click
      this.#say(error.message);
    } finally {
      this.#busy(false);
    }
  }

  #busy(state) {
    if (!this.button) return;
    this.button.disabled = state;
    this.button.setAttribute('aria-busy', String(state));
  }

  #say(message) {
    if (this.status) this.status.textContent = message;
  }
}

customElements.define('quick-order', QuickOrder);

The location decides, not the customer

The problem

A B2B buyer purchases for several company locations, and the location decides the catalog, the price list and the payment terms. If the picker hides in the account page, the cart fills up with the wrong prices.

The catch

customer.b2b? is false for logged-out visitors, so on its own it won't hide prices from the public. And switching location switches the whole catalog: anything cached per page has to vary with it.

snippets/b2b-location-picker.liquid
{%- comment -%}
  A B2B buyer belongs to a company and often buys for more than one location. The
  catalog, the price list and the payment terms all follow the location, so the
  picker belongs in the header, before the cart fills up with the wrong prices.

  Objects: customer.b2b?, customer.current_company, customer.current_location,
  customer.company_available_locations, location.url_to_set_as_current.
{%- endcomment -%}

{%- if customer.b2b? -%}
  {%- assign locations = customer.company_available_locations -%}

  <div class="b2b-bar">
    <p class="b2b-bar__who">
      {{ customer.current_company.name }} ·
      <strong>{{ customer.current_location.name }}</strong>
    </p>

    {%- if locations.size > 1 -%}
      {%- comment -%} A details element: it works before the JS bundle arrives. {%- endcomment -%}
      <details class="b2b-bar__picker">
        <summary>{{ 'b2b.location.change' | t }}</summary>
        <ul>
          {%- for location in locations -%}
            {%- unless location.current? -%}
              <li>
                <a href="{{ location.url_to_set_as_current }}">
                  {{ location.name }}
                  <span>{{ location.shipping_address.city }}</span>
                </a>
              </li>
            {%- endunless -%}
          {%- endfor -%}
        </ul>
      </details>
    {%- endif -%}
  </div>
{%- endif -%}

This site, from the inside

A slow portfolio would be a contradiction. Here are the numbers, and what sits behind them.

Performance
96
Accessibility
100
Best practices
100
SEO
92
LCP
2 s
CLS
0
TBT
0 ms
Weight
299 KiB
Requests
20

Lighthouse 12, emulated mobile on 4G, measured on 21 October 2026 against https://nicolasmateos.com/. Check it yourself on PageSpeed Insights ↗

The decisions

  • Static HTML. Astro builds the pages; the browser gets no framework, only the small amount of JavaScript behind the menu, the project modal and the form.
  • Images are generated before the build with sharp (AVIF and WebP, three widths) and served with width and height. That is why CLS is 0.
  • Variable fonts from this domain, preloading only the one that paints the headline.
  • Cloudflare serves the built files; the ones with a hash in the name go out immutable, cached for a year.
  • The Checkout Lab, the heaviest thing on the home page, is not downloaded until you are 600 px away from it, and it stays inert until it answers: no keyboard focus and no screen-reader output for a widget that cannot respond yet.

Got a Shopify project?

Get in touch