Self-hosting Hydrogen
This guide might not be compatible with features introduced in Hydrogen version 2025-05 and above. Check the latest documentation if you encounter any issues.
If you don’t want to deploy to Oxygen, then you can host Hydrogen on other JavaScript runtimes, such as Vercel, Netlify, Fly.io, and Cloudflare Workers.
Anchor to RequirementsRequirements
- Install the Headless channel. This enables access to the Storefront API and Customer Account API, and lets you manage API tokens.
Anchor to Steps to update your Hydrogen appSteps to update your Hydrogen app
Your hosting platform might have other requirements or implementation details not covered here.
Anchor to Step 1: Update your Remix adapterStep 1: Update your Remix adapter
Remix, Hydrogen’s underlying framework, provides a range of adapters that allow you to adjust your app configuration and build step to target different deployment runtimes.
- In your Hydrogen app, uninstall the Remix adapter for Oxygen:
- Install the Remix adapter for your selected hosting platform. For example:
Anchor to Step 2: Edit app files for your adapterStep 2: Edit app files for your adapter
Typically, when changing Remix adapters, you’ll need to make updates to the following files:
server.js
remix.config.js
app/entry.server.jsx
Remix provides templated examples for a range of adapters. Consult your chosen adapter's template files for specific configurations or required edits.
When making changes, make sure that you continue to pass Hydrogen’s Storefront API client to the Remix loader context. Typically, this involves editing the updated server.js
file using the createRequestHandler
function provided by your chosen adapter.
The following example shows some changes that your app might require:
File
/server.js
import {createStorefrontClient} from '@shopify/hydrogen';
import {createRequestHandler} from '@remix/ADAPTER_NAME';
export default {
async fetch(request, env, executionContext) {
const {storefront} = createStorefrontClient({
// Required: Storefront API credentials
privateStorefrontToken: env.PRIVATE_STOREFRONT_API_TOKEN,
publicStorefrontToken: env.PUBLIC_STOREFRONT_API_TOKEN,
storefrontApiVersion: env.PUBLIC_STOREFRONT_API_VERSION,
storeDomain: `https://${env.PUBLIC_STORE_DOMAIN}`,
storefrontHeaders: {
// Pass a buyerIp to prevent being flagged as a bot
buyerIp: 'customer_IP_address', // Platform-specific method to get request IP
cookie: request.headers.get('cookie'), // Required for Shopify Analytics
purpose: request.headers.get('purpose'), // Used for debugging purposes
},
i18n: {
country: 'country_code',
language: 'language_code',
},
cache: () => {},
waitUntil: () => {},
// Additional platform-specific configuration...
});
const handleRequest = createRequestHandler({
// Inject the Storefront API client into the Remix context
getLoadContext: () => ({storefront}),
// Additional platform-specific configuration...
});
return handleRequest(request);
},
};
import {createStorefrontClient} from '@shopify/hydrogen';
import {createRequestHandler} from '@remix/ADAPTER_NAME';
export default {
async fetch(request: Request, env: Env, executionContext: ExecutionContext) {
const {storefront} = createStorefrontClient({
// Required: Storefront API credentials
privateStorefrontToken: env.PRIVATE_STOREFRONT_API_TOKEN,
publicStorefrontToken: env.PUBLIC_STOREFRONT_API_TOKEN,
storefrontApiVersion: env.PUBLIC_STOREFRONT_API_VERSION,
storeDomain: `https://${env.PUBLIC_STORE_DOMAIN}`,
storefrontHeaders: {
// Pass a buyerIp to prevent being flagged as a bot
buyerIp: 'customer_IP_address', // Platform-specific method to get request IP
cookie: request.headers.get('cookie'), // Required for Shopify Analytics
purpose: request.headers.get('purpose'), // Used for debugging purposes
},
i18n: {
country: 'country_code',
language: 'language_code',
},
cache: () => {},
waitUntil: () => {},
// Additional platform-specific configuration...
});
const handleRequest = createRequestHandler({
// Inject the Storefront API client into the Remix context
getLoadContext: () => ({storefront}),
// Additional platform-specific configuration...
});
return handleRequest(request);
},
};
The cache
object should implement the standard Web Cache API. Check with your chosen hosting platform for more details about its caching implementation.
The waitUntil
method is used in serverless contexts to keep the request function alive after a response has been sent. Check whether your hosting platform supports this pattern, or one like it. For example, Vercel Edge Functions provide a compatible waitUntil
method. This method isn't required when deployed to a stateful Node.js server, because the server keeps running after the response has been returned.
Consult the createStorefrontClient
API reference for the complete list of required and optional parameters.