

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:
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.
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:

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
Hydrogen Users
Shopify Devs
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.
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!
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.
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:
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:
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)
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:
Soft Wear update is available pic.twitter.com/VhVbdVV8GN
— Remix 💿 (@remix_run) May 19, 2025
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.

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:
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:

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.
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.

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.
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.
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.

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.
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.

The hero is one giant scroll-synced experience:
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" }}
/>
));
}

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>
);

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>
);

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")}
/>
</>
);

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
}
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:
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).