Shopify powers millions of cross-border e-commerce stores — including thousands targeting Chinese consumers. But Shopify’s global CDN infrastructure, while excellent for North American and European users, delivers frustratingly slow page loads to Chinese shoppers: 4–8 second load times from Shopify’s standard infrastructure reaching China are common, and every additional second costs conversion rate.
Shopify Headless solves this problem: use Shopify as the commerce backend (inventory, orders, payments, checkout) but deploy a custom frontend — built with Next.js and the Shopify Storefront API — on a Hong Kong VPS with CN2 GIA routing. Chinese shoppers load your storefront from a server 130 km from Shenzhen instead of from a data centre in Virginia or Frankfurt.
Why Headless Solves the China Performance Problem
Standard Shopify stores serve all pages — including the product catalogue, collection pages, and homepage — from Shopify’s infrastructure. Chinese users connect to Shopify’s nearest CDN PoP (typically in Japan or Singapore), introducing 60–150 ms of base latency before any page content is served.
With a headless architecture:
- Your Next.js frontend runs on a Hong Kong VPS with CN2 GIA routing → 20–35 ms to Chinese users
- The frontend calls the Shopify Storefront API at build time (static generation) or with short-lived caching — not on every user request
- Product pages are served as pre-rendered HTML from your Hong Kong server, not dynamically from Shopify’s infrastructure
- Checkout still happens on Shopify’s domain — the cart and payment flow uses Shopify’s infrastructure, which is fine for the 2–3 steps of checkout
The result: product browsing (where 80% of sessions never progress) is fast from China; checkout (where users are already committed) uses Shopify’s robust global infrastructure.
Step 1: Set Up Shopify Storefront API Access
- In your Shopify Admin → Apps → Develop apps → Create an app
- Configure API Scopes → Storefront API access → enable:
unauthenticated_read_product_listings,unauthenticated_read_product_inventory,unauthenticated_read_collection_listings,unauthenticated_write_checkouts - Install app and copy the Storefront API access token
Step 2: Create a Next.js Shopify Storefront
# On your local development machine
npx create-next-app@latest my-shopify-store --typescript --tailwind --app
cd my-shopify-store
npm install @shopify/hydrogen-react graphql-requestnano .env.localSHOPIFY_STORE_DOMAIN=your-store.myshopify.com
SHOPIFY_STOREFRONT_ACCESS_TOKEN=your_storefront_api_token
NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN=your-store.myshopify.com
NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN=your_storefront_api_tokenCreate Shopify API client
mkdir -p lib
nano lib/shopify.tsconst domain = process.env.SHOPIFY_STORE_DOMAIN!;
const storefrontAccessToken = process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN!;
const endpoint = `https://${domain}/api/2024-04/graphql.json`;
interface ShopifyFetchOptions {
query: string;
variables?: Record<string, unknown>;
}
export async function shopifyFetch({ query, variables }: ShopifyFetchOptions): Promise {
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Shopify-Storefront-Access-Token': storefrontAccessToken,
},
body: JSON.stringify({ query, variables }),
next: { revalidate: 60 } // Cache for 60 seconds — reduces Shopify API calls
});
if (!response.ok) throw new Error(`Shopify API error: ${response.status}`);
const { data } = await response.json();
return data;
}
// Fetch all products
export async function getProducts() {
const data = await shopifyFetch({
query: `{
products(first: 20) {
edges {
node {
id
title
handle
description
priceRange {
minVariantPrice { amount currencyCode }
}
images(first: 1) {
edges { node { url altText } }
}
variants(first: 1) {
edges { node { id availableForSale } }
}
}
}
}
}`
});
return data.products.edges.map((edge: any) => edge.node);
}
// Fetch single product by handle
export async function getProduct(handle: string) {
const data = await shopifyFetch({
query: `query getProduct($handle: String!) {
product(handle: $handle) {
id title description handle
priceRange { minVariantPrice { amount currencyCode } }
images(first: 5) { edges { node { url altText } } }
variants(first: 10) {
edges { node { id title price { amount } availableForSale selectedOptions { name value } } }
}
}
}`,
variables: { handle }
});
return data.product;
}Create product listing page with ISR (Incremental Static Regeneration)
nano app/products/page.tsximport { getProducts } from '@/lib/shopify';
import Link from 'next/link';
import Image from 'next/image';
// Revalidate every 5 minutes — products update without full rebuild
export const revalidate = 300;
export default async function ProductsPage() {
const products = await getProducts();
return (
<main className="max-w-6xl mx-auto px-4 py-8">
<h1 className="text-3xl font-bold mb-8">Our Products</h1>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
{products.map((product: any) => (
<Link key={product.id} href={`/products/${product.handle}`}>
<div className="border rounded-lg overflow-hidden hover:shadow-lg transition-shadow">
{product.images.edges[0] && (
<Image
src={product.images.edges[0].node.url}
alt={product.images.edges[0].node.altText || product.title}
width={400}
height={400}
className="w-full object-cover"
/>
)}
<div className="p-4">
<h2 className="font-semibold text-sm">{product.title}</h2>
<p className="text-gray-600 text-sm mt-1">
{product.priceRange.minVariantPrice.currencyCode}{' '}
{parseFloat(product.priceRange.minVariantPrice.amount).toFixed(2)}
</p>
</div>
</div>
</Link>
))}
</div>
</main>
);
}Step 3: Deploy to Hong Kong VPS
# On your Hong Kong VPS
apt install -y nodejs npm
npm install -g pm2
mkdir -p /home/deploy/apps/shopify-store
cd /home/deploy/apps/shopify-store
# Clone your repository or transfer files
git clone https://github.com/yourusername/my-shopify-store.git .
npm install
# Create production .env file
nano .env.productionSHOPIFY_STORE_DOMAIN=your-store.myshopify.com
SHOPIFY_STOREFRONT_ACCESS_TOKEN=your_storefront_api_token
NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN=your-store.myshopify.com
NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN=your_storefront_api_token# Build the Next.js application
npm run build
# Start with PM2
pm2 start npm --name "shopify-store" -- start
pm2 saveConfigure Nginx for the Next.js store
nano /etc/nginx/sites-available/shopify-storeserver {
listen 80;
server_name store.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name store.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/store.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/store.yourdomain.com/privkey.pem;
# Cache static Next.js assets aggressively
location /_next/static/ {
proxy_pass http://127.0.0.1:3000;
expires 1y;
add_header Cache-Control "public, immutable";
}
# Product images from Shopify CDN — proxy with caching
location /images/ {
proxy_pass http://127.0.0.1:3000;
expires 30d;
add_header Cache-Control "public";
}
# Next.js application
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 30s;
}
gzip on;
gzip_types text/plain text/css application/json application/javascript;
}ln -s /etc/nginx/sites-available/shopify-store /etc/nginx/sites-enabled/
certbot --nginx -d store.yourdomain.com --email your@email.com --agree-tos --no-eff-email
nginx -t && systemctl reload nginxStep 4: Performance Comparison — Before and After
Test your store from Chinese locations using 17ce.com (multi-node China speed test):
| Metric | Standard Shopify | Headless on HK VPS (CN2 GIA) |
|---|---|---|
| TTFB from Shanghai | 800–2,000 ms | 80–200 ms |
| Full page load (product page) | 4–8 seconds | 1–2.5 seconds |
| Google Core Web Vitals (LCP) | Poor (>4s) | Good (<2.5s) |
| Mobile conversion rate (est.) | Baseline | +10–20% improvement |
Handling Shopify Checkout from China
The checkout process (cart → payment → order confirmation) still happens on Shopify’s domain (checkout.shopify.com). Chinese users experience Shopify’s standard checkout latency for this 2–3 step process. Since users who reach checkout are already committed to purchasing, the slightly higher latency at checkout is generally acceptable.
To redirect users from your HK VPS frontend to Shopify checkout:
// Create checkout mutation via Storefront API
export async function createCheckout(variantId: string, quantity: number) {
const data = await shopifyFetch<any>({
query: `mutation checkoutCreate($input: CheckoutCreateInput!) {
checkoutCreate(input: $input) {
checkout { id webUrl }
checkoutUserErrors { field message }
}
}`,
variables: {
input: {
lineItems: [{ variantId, quantity }]
}
}
});
// webUrl points to Shopify's checkout — redirect user there
return data.checkoutCreate.checkout.webUrl;
}
// In your "Buy Now" button handler:
const checkoutUrl = await createCheckout(variantId, 1);
window.location.href = checkoutUrl; // Redirect to Shopify checkoutConclusion
A Shopify headless storefront on a Hong Kong VPS dramatically improves the browsing experience for Chinese shoppers — reducing product page load times from 4–8 seconds to 1–2.5 seconds, improving Core Web Vitals scores, and delivering the conversion rate improvement that faster pages consistently produce. The architecture is clean: Next.js handles presentation from your Hong Kong server, Shopify handles commerce backend reliably, and users get the best of both.
Deploy your headless Shopify frontend on Server.HK’s Hong Kong VPS plans — CN2 GIA routing delivers the 20–35 ms latency to Chinese users that makes the page speed difference visible and measurable.
Frequently Asked Questions
Do I need a Shopify Plus plan for headless commerce?
No. The Shopify Storefront API is available on all Shopify plans including Basic. Shopify Plus adds additional API capabilities and custom checkout extensibility, but a functional headless storefront for product browsing and cart creation works on any plan. The checkout flow always uses Shopify’s standard checkout infrastructure regardless of plan level.
How does inventory sync work between the HK VPS frontend and Shopify?
The Next.js frontend fetches product and inventory data from Shopify’s Storefront API with ISR (Incremental Static Regeneration). With revalidate: 300, product pages are regenerated from Shopify’s current data every 5 minutes. For real-time inventory accuracy, use client-side SWR or React Query to fetch current availability on page load — a hybrid of static generation for SEO and client-side fetching for live inventory.
Can I use this approach with other e-commerce platforms besides Shopify?
Yes. The same pattern applies to any headless-capable commerce platform: WooCommerce (via REST API), Magento (via GraphQL), BigCommerce (via Storefront API), or a custom backend. The key is separating the commerce logic (on whatever platform you use) from the customer-facing frontend (deployed on your Hong Kong VPS for China performance).