Remix Website homepage
December 8, 2025

Open Sourcing the Remix Store

Brooks Lybrand
Developer Relations Manager

Today we are open sourcing the Remix Store. We think making the store's source code public will benefit developers building with React Router as well as developers building Shopify stores.

The Remix Team is an open source team. We work on open source software like react-router and remix. Sometimes we open source our websites too.

We launched the store back in May, and the intention was always to open source it. I delayed because with something like a store where there are actual exchanges of money for goods (something open source folks rarely know much about), there's a lot more room for abuse if done wrong.

I think I've got everything sorted out to keep our store safe while also allowing anyone to run it locally and poke around/contribute.

With the opening up of the repo, I wanted to take a moment to share:

  • Who this is for
  • What it takes to launch a store
  • Some cool features worth digging into

If you just want to skip the backstory and dig into the recipe, check out the source code on GitHub.

Otherwise, let's dig into the details of procurement requests, spinning hoodies, and the many iterations it took to get things right.

Who this is for

Before we dig into the technical bits, it's always good to put on our Product Manager hat and consider why we're building what we're building.

Launching a swag store was one of the first things my manager (Michael Jackson) asked me to do when I first joined the Remix Team in 2023. In fact, this wasn't even the first attempt. Digging through some of our old, private repos, I stumbled across this beauty:

GitHub commit history page for remix-run/swag-store showing early development commits from August 2022, including form components, Dialog, CartItem, and various UI tweaks.

To put things in context, this first store was started pre-Shopify acquisition, and well before I joined the team.

After Remix joined Shopify, the reasons to build a store increased, especially with Hydrogen being built on Remix v1-2 (and now React Router). As Michael and I discussed it, we realized that we essentially had 3 audiences. The following comes directly from the internal project I had to set up to convince Shopify to let us use company resources to launch the store (more on that later):

Remix Users

  • Fans can purchase swag to rep their favorite framework (one of our most frequent requests) and organically advertise our software
  • Sending "thank you"s to contributors
  • Streamline sending swag to meetups and conferences

Hydrogen Users

  • Real-world prod example of a Hydrogen store
  • Open-sourced for education purposes

Shopify Devs

  • Real world app to dog-food new Remix and Hydrogen features
  • Cross collaborate between the Hydrogen and Remix teams to learn from one another
  • Educate and connect the Remix team better with the core of what Shopify

The store has now been up for over half a year and we've received and fulfilled over 200 orders. We have future drops and improvements planned (cough cough more affordable international shipping cough cough). I love seeing people enjoy and represent our merch.

We have also been able to use the store to test out new React Router things like upgrading Hydrogen apps to React Router v7, middleware, and React Server Components.

Remix Store built on React Server Components example
Jacob Ebey's reimagining of the Remix Store using React Server Components

The only thing we haven't done is share the store's source code for React Router and Shopify developers to learn from.

Back when we open sourced remix.run, I shared this sentiment:

Personally, I wanted to make open source contributions years before I worked up the courage. While this says more about me than anything, one thing I know that would have made it easier is if I contributed to an open source website vs a library. Why? Because I worked on websites all day long at my job, it's what I already knew. I didn't write libraries. In fact, far fewer web developers write libraries than write websites.

I think this still holds true, and I hope by airing out code I (and Cursor, thanks buddy) wrote for the Remix Store will inspire others. Some of the code is good, some of it is mediocre, some of it is hacky, and some of it was just to get things over the finish line. Take a look at it, judge it, learn from it, improve it.

The store is for developers using React Router, building on Shopify, and eventually will be rewritten on our new Remix 3 work.

So dig in!

What it takes to launch a store

In this section I want to give a bit of the history and share what I learned about running what is basically a mini business.

Using Shopify

I was pretty ignorant about what it would take to launch a store when I first started. I was also honestly pretty ignorant about Shopify and its capabilities. Despite being a Shopify employee, I was very much learning Shopify as I went.

The good news is using Shopify's platform was probably the easiest part of this whole process. For our store, Shopify handles:

  • Inventory management
  • Customer Management
  • Hosting (in our case on Oxygen)
  • Email marketing
  • Analytics

And a ton more I won't dig into. Using Shopify's platform is probably the most uninteresting part of the journey, because it was the most straightforward. My experience with Shopify was always:

  1. Have a problem or need
  2. Look around in the admin to see if there's a tab for what I need
  3. If step 2 fails, ask sidekick

I am still far from a Shopify expert, and I solved 95% of my issues just doing this with no prior Shopify experience (besides the very basic Shopify store setup they make you do during orientation)

Adding Tim

I joined Shopify in September of 2023. Despite the store being one of the first big initiatives Michael gave me, I didn't get meaningfully started on it until the Summer of 2024.

The reason for this was pretty simple: I had no design support until then.

With the Remix store, we wanted both our website and our products to be unique and well designed. Our launch video showcases the final product well:

Left to myself, the store would have been generic and the t-shirts even more so. That would have been passable, but it would miss the mark of what we were trying to do with the store (see the prior section).

Enter our beloved design and brand extraordinaire, Tim Quirino! Tim joined our team via another acquisition. Prior to joining us, Tim had never worked with a developer-heavy, open source team. However, we couldn't have asked for a better fit. Even though Tim has considerable experience designing software products, brand, and merch at Meta & Threads, he started his career designing merch for bands and record labels.

I was stoked to have Tim on the team, and couldn't wait to start talking about the store with him. In fact, our first Slack messages were about the Remix Store.

Screenshot of a Slack DM conversation between Brooks Lybrand and Tim Quirino discussing the Remix Swag Store hack day project, with Tim expressing interest in collaborating.

Hack Days Project

As hinted at in that Slack conversation, my first attempt to actually build the Remix Store centered largely around seeing if I could trick other people into doing all the work. That's what hackathons and Hack Days are for, right?

I had 12 people sign up to be on my team to build the first version of the store, and I couldn't have been more pleased. This gave Tim very little time to gather the context he needed and design a first version of the store, but he's a professional, so it's fine (I have since grown in my appreciation for how much effort and inspiration goes into the design process).

I'm incredibly grateful to all the Shopify folks who helped out with the project. With little more than the Hydrogen starter template, we divvied up tasks to see how much we could knock out in 2.5 days.

Some challenges we faced:

  • Tim could not make it in person so we were basically just running with his Figma designs
  • The wifi did not work the first day and kind of worked the second and third day
  • We had a wide variety of skill sets on our team, and I did a terrible job providing structure for effective parallel work

Despite all this, we actually got decently far.

You can try to dig through the git commits if you want, or you can just check out the old Figma project. The first version of the store looked something like this:

Side-by-side dark and light mode views of v0 of The Remix Store website featuring a mini skateboard banner and six product listings including a jacket, hip pack, shirt, skateboard, notebook, and gel pen.

The visual direction I gave Tim was something along the lines of "retro, digital, music, synth pads, drum machines, color". He did a great job for what I gave him, and I need to stress again, I gave him little and even less time.

Paying for the store

After the Hack Days project I realized I would actually have to keep building the store on my own (for some reason people from other teams didn't want to endlessly donate extra time to me). I made good progress in implementing what Tim had initially designed while Tim noodled on different product ideas and designs.

We got some feedback from Michael on the design, and went for a less chunky, more sleek feel. You can also check out those Figma designs.

Screenshot of v1 of The Remix Store website in dark mode, featuring a black t-shirt with a colorful Remix logo in the hero section and a shopping cart overlay on the right. The cart contains a shirt, a mini skateboard, and a gel pen, totaling $93.00.

We were feeling pretty good about ourselves, and had some great product ideas in place (pretty much everything currently listed on the store). The only thing we needed was some money from Shopify to get the store going.

Mona-Lisa Saperstein from Parks and Rec saying 'Money pleeeease' while gesturing for money to be given to her.

Quite reasonably, big companies such as ours don't necessarily just write blank checks and actually want to know what you intend to do with their resources and how it will go back to supporting the underlying mission. In retrospect that's pretty reasonable, but it did require a bit more work internally on our end.

Without boring you with all the inner workings of Shopify (or divulging information I'm probably not legally allowed to share), we basically had to set up an internal project in order to go through the proper procurement process. In general our team is super fortunate to get to run our own roadmap and leverage our own processes, since all of the work we're doing is open source.

With the internal project created and the justifying of our store's existence done, we were able to move forward with the vendor we had selected to run the production and fulfillment side of our store.

Feedback is a Gift

With the Remix Store, we were asking for more resources, and we were also representing Shopify in a new way by putting out a storefront built on Shopify. Understandably there was a little more input needed.

We set up the internal project and got procurement worked out in late October, 2024. However, when we showed off our nearly complete storefront, we received some hard, but needed feedback from one of the Shopify VPs:

Please push yourself to flex a little more and remember that you are the team that built remix.run which is one of my favorite dev marketing websites that does a great job of telling me what the Remix brand is.

The Remix homepage is such a delight to scroll through, and did such a fantastic job explaining what Remix was offering (we're planning for our next homepage to do just as good of a job with Remix 3). Basically what we had created didn't immediately show the value. It looked somewhat generic and simple, and in no way justified the need to build it as a custom storefront.

So we went back to the drawing board.

At this point Tim and I had a lot more experience working with each other, and instead of painstakingly describing and documenting everything in Figma, Tim was able to leverage another one of his favorite tools: Framer.

Tim was able to make a much more high fidelity prototype of the store, which I could then inspect, optimize, and build with Remix and Hydrogen.

The end result is what you see now when you visit shop.remix.run.

Scrolling through the remix store with the spinning hoodie and large product photos

We iterated for another 6 months: finalizing contracts, selecting blanks, adding features, getting feedback, evaluating test prints, and planning our marketing strategy, all culminating in launching the Remix Store on May 19, 2025.

Some cool features

There are a lot of little features and animations I'm particularly proud of in this store. I wanted to highlight a few of the fun features and good user experiences we built. This is not a comprehensive walk-through, just a high-level overview and glimpse at the relevant code. Feel free to explore more since, ya know, it's now open source.

Hero

3D hoodie spinning in the hero section

The hero is one giant scroll-synced experience:

  • The 3D hoodie asset gloriously rotates as you scroll
  • The glowing "REMIX" light syncs up with the rotating hoodie and fades out
  • The highlight of "SOFTWARE ..." to "SOFT WEAR..." switches as the hoodie's back becomes visible

The hook useScrollPercentage tracks and returns a value from 0 to 1, which is then used to drive the state of all the other elements.

The hoodie itself is just 61 different images that we preload and swap out as you scroll, a method we stole from Apple's previous AirPod product page.

// Pick the frame based on how far you've scrolled
let frameIndex = Math.min(
  Math.floor(scrollPercentage * 1.5 * assetImages.length),
  assetImages.length - 1,
);

return (
  <RotatingProduct
    product={product}
    assetImages={assetImages}
    frameIndex={frameIndex}
  />
);

// Inside RotatingProduct: swap frames instead of re-rendering a canvas
{
  assetImages.map((asset, index) => (
    <img
      key={asset.image.url}
      src={asset.image.url}
      className="absolute inset-0 mx-auto object-cover"
      style={{ visibility: index === frameIndex ? "visible" : "hidden" }}
    />
  ));
}

Collections exploder header

Collection header text duplicating and expanding on the top and bottom as user scrolls

The collection pages header "explodes" by duplicating the title in five stacked colors and sliding each layer at a different rate as you scroll (thanks again, useScrollPercentage()). It's a tiny parallax effect that keeps the heading readable while giving the page motion without video or heavy assets.

let translatePercent = Math.round(Math.min(scrollPercentage * 2, 1) * 80);

return (
  <div className="font-title relative w-full text-center uppercase">
    <h1 className="relative z-50 bg-black text-white">{children}</h1>
    {["pink", "red", "yellow", "green", "blue"].map((color, i) => (
      <span
        key={color}
        aria-hidden
        className={`text-${color}-brand absolute inset-0`}
        style={{ transform: `translateY(${(i - 2) * translatePercent}%)` }}
      >
        {children}
      </span>
    ))}
  </div>
);

Product image blur loader

Blurred product image on initial load, revealing the full image once it's loaded

On the product pages we request a 32px version of each product image for an instant, blurred preview, then crossfade to the full image once it finishes loading. Shopify's image CDN makes it really easy to request any version of an image you need, which Hydrogen's <Image /> component takes advantage of to generate an appropriate srcset to deliver the appropriate image based on the user's screen size.

const imageRef = useRef<HTMLImageElement>(null);
const [loadState, setLoadState] = useState<"pending" | "loaded" | "error">(
    "pending",
  );

const previewUrl = data.url.includes("?")
  ? `${data.url}&width=32`
  : `${data.url}?width=32`;

useLayoutEffect(() => {
  const node = imageRef.current;
  if (!node) return;
  if (loadState !== "pending") return;
  if (node.complete) {
    setLoadState("loaded");
    return;
  }
  node.onload = () => setLoadState("loaded");
  node.onerror = () => setLoadState("error");
  return () => {
    node.onload = null;
    node.onerror = null;
  };
}, [loadState]);

return (
  <>
    {/* Blurred preview image */}
    <img
      src={previewUrl}
      alt={alt}
      className={clsx(
        "absolute inset-0 size-full object-cover blur-2xl transition-opacity duration-750",
        loadState === "loaded" ? "opacity-0" : "opacity-100",
      )}
      draggable={false}
      aria-hidden={true}
    />
    {/* Full image */}
    <HydrogenImage
      ref={imageRef}
      data={data}
      className={clsx(
        "relative h-full w-full object-cover transition-all duration-750",
        loadState === "loaded" ? "blur-none" : "blur-2xl",
      )}
    />
  </div>
);

Optimistic cart

Optimistic cart UI showing items in the cart before they are added to the database

To make sure the cart experience feels snappy and pleasant, we take advantage of Hydrogen's useOptimisticCart hook. The user can rapidly add/remove items from the cart and we show them the most likely end state. To indicate pending updates, we shade prices and update the checkout button's text to "Updating cart...", and then resolve once all loaders settle and the latest data is available.

let cart = useOptimisticCart(rootData?.cart);
let isOptimistic = Boolean(cart?.isOptimistic);
let totalQuantity = cart.totalQuantity || 0;

let lines = cart.lines.nodes;
let isOptimistic = Boolean(cart.isOptimistic);

let subtotalAmount = cart?.cost?.subtotalAmount;

if (!cart) return <EmptyCart>;

return (
  <>
    {/* ... */}
    {lines.map((line) => (
      <CartLineItem
        key={line.id}
        line={line}
        isOptimistic={isOptimistic}
        className="gap-4"
      />
    ))}
    {/* ... */}
    <Money
      data={subtotalAmount}
      className={clsx("text-base font-bold", isOptimistic && "text-white/50")}
    />
  </>
);

404/500 glitchy text

Green "404" made up of numbers that are randomly changing

Our error pages render “404”/“500” as a matrix of hex digits. We convert a PNG sprite into a grid of numbers that randomly flip on a timer for a low-fi glitch.

const textToImageMap = {
  "404": error404Src,
  "500": error500Src,
  empty: emptySrc,
};

export function MatrixText({ text }: { text: TextId }) {
  const { dataUrl, scale, matrixTextData } = useMatrixValues(text);
  const glitchedText = useGlitchText(matrixTextData);

  return (
    <div>
      {/* Blurred background layer */}
      <div
        className={clsx(wrapperCss, "blur-xl")}
        style={{
          transform: `translate(-50%, -50%) scale(${scale})`,
          imageRendering: "pixelated",
          backgroundImage: `url(${dataUrl})`,
        }}
      >
        {matrixTextData?.text}
      </div>
      {/* Foreground glitched text layer */}
      <div
        className={wrapperCss}
        style={{
          transform: `translate(-50%, -50%) scale(${scale})`,
          imageRendering: "pixelated",
          backgroundImage: `url(${dataUrl})`,
        }}
      >
        {glitchedText}
      </div>
    </div>
  );
}

function useMatrixValues(text: TextId) {
  // Loads the image, converts it to a canvas, then processes each pixel
  // to generate a grid of hex characters representing the image brightness
  // Also calculates the scale needed to fit the text to the viewport width
  // Returns: { dataUrl, scale, matrixTextData }
}

function useGlitchText(matrixData: MatrixTextData) {
  // Randomly flips ~8% of the active hex characters on a timer
  // Uses requestAnimationFrame with setTimeout throttling for smooth animation
  // Respects prefers-reduced-motion setting
}

Future improvements and contributing

Like I've said a few times, we're pretty happy with what we've launched. There's still a lot we'd like to do to keep improving it, both in terms of user experience as well as customer experience. In no particular order, we are already planning to:

  • Drop some new products before the end of 2025
  • Update the home page
  • Offer more affordable shipping options for international orders
  • Rewrite the store in Remix 3

That's pretty much it for the story of the Remix Store! If you notice any bugs please open an issue and/or pull request.

Oh also, if you made it this far, feel free to take 15% off your next order (code: OPEN_SOURCE, available until the end of 2025).


Get updates on the latest Remix news

Be the first to learn about new Remix features, community events, and tutorials.