Encited

Pre-rendering for Shopify through Cloudflare

How Shopify pre-rendering works#

Shopify serves your store from its own edge, so you cannot point your domain anywhere else without breaking checkout. Instead, your domain stays on Cloudflare pointing at Shopify, and a Worker route intercepts crawler requests before they reach the store.

Crawler GET requests for HTML receive the rendered snapshot from Encited. Browser visits, assets, and any failed render calls continue through Cloudflare to Shopify unchanged.

Prerequisites#

  • An Encited account and API key (Settings → API Keys).
  • Your store's custom domain with DNS managed in a Cloudflare account.
  • The domain connected to Shopify as usual (A record on the apex, CNAME on www).

Set up pre-rendering for Shopify#

Your DNS stays exactly as Shopify configured it: an A record on the apex pointing at Shopify and a CNAME on www pointing at shops.myshopify.com. The Worker route sits in front of them, so keep your custom domain set as the primary domain in Shopify admin.

1. Create a new Worker#

Open Cloudflare dashboard → choose a "Hello World" Worker → Deploy → Edit Code

2. Paste the snippet below into the Worker editor#

encited

// encited.js (Cloudflare Worker)
export default {
  async fetch(req, env) {
    // Only handle public GET navigations
    if (req.method !== 'GET') return fetch(req);

    // Treat missing/empty Accept and bare '*/*' as HTML so crawler tests
    // (curl without -H, default fetch) still route through prerender.
    // Asset requests from browsers send specific Accept (e.g. 'text/css,*/*;q=0.1')
    // so they won't match.
    const accept = (req.headers.get('accept') || '').trim();
    const isHtmlRequest = !accept || accept === '*/*' || accept.includes('text/html');
    if (!isHtmlRequest) return fetch(req);

    const headers = new Headers();
    // Add LOVABLEHTML_API_KEY as a secret: Worker -> Settings -> Variables and Secrets.
    headers.set('x-lovablehtml-api-key', env.LOVABLEHTML_API_KEY);
    headers.set('accept', 'text/html');
    const forward = [
      'accept-language',
      'sec-fetch-mode',
      'sec-fetch-site',
      'sec-fetch-dest',
      'sec-fetch-user',
      'upgrade-insecure-requests',
      'referer',
      'user-agent',
    ];
    for (const name of forward) {
      const v = req.headers.get(name);
      if (v) headers.set(name, v);
    }

    try {
      const r = await fetch(
        'https://encited.com/api/prerender/render?url=' + encodeURIComponent(req.url),
        { headers, redirect: 'manual' },
      );

      // 301 = configured redirect rule matched - forward to client
      if (r.status === 301) {
        const loc = r.headers.get('location');
        if (loc) {
          return new Response(null, {
            status: 301,
            headers: { location: loc, 'cache-control': 'no-store' },
          });
        }
      }

      // 304 = not pre-rendered, pass through to origin
      if (r.status === 304) {
        return fetch(req);
      }
      if (r.status === 200 && (r.headers.get('content-type') || '').includes('text/html')) {
        const responseHeaders = new Headers(r.headers);
        for (const name of ['content-encoding', 'content-length', 'transfer-encoding', 'connection', 'keep-alive']) {
          responseHeaders.delete(name);
        }
        responseHeaders.set('content-type', 'text/html; charset=utf-8');
        return new Response(r.body, { status: 200, headers: responseHeaders });
      }
    } catch {
      // Prerender unreachable → fall through so visitors still get the site
    }
    return fetch(req);
  },
};

3. Add a route to your Worker#

Go to your Worker → Settings → Domains & Routes → Add Route → enter yourdomain.com/*. If your site is reachable on both yourdomain.com and www.yourdomain.com, enter *yourdomain.com/* instead so both are covered.

4. Deploy the Worker#

It can take a couple of minutes to start working.

Common mistakes

  • DNS record on the gray cloud. Routes only fire on proxied traffic. In DNS → Records, the A or CNAME record for yourdomain.com must show the orange cloud (Proxied), not gray (DNS only).
  • Failure mode left on the default. Set the route's Failure mode to Fail open (proceed) so a Worker error sends requests straight to your origin instead of failing.
  • Bot Fight Mode blocking crawlers. Bot Fight Mode and AI crawler blocking (Security → Bots) run before your Worker, so a blocked crawler never reaches it. Allow the crawlers you want pre-rendered.

Test the integration#

1. Send a crawler request

curl -sS -D - -o /dev/null \
  -A "Googlebot" \
  -H "Accept: text/html" \
  "https://your-domain.com/"

Look for a successful HTML response and the x-lovablehtml-render-cache header.

2. Confirm browser passthrough

curl -sS -D - -o /dev/null \
  -A "Mozilla/5.0" \
  -H "Accept: text/html" \
  -H "Accept-Language: en-US" \
  -H "Sec-Fetch-Mode: navigate" \
  -H "Sec-Fetch-Dest: document" \
  -H "Upgrade-Insecure-Requests: 1" \
  "https://your-domain.com/"

This request should come back from Shopify without an Encited render-cache header.

Common errors#

The Worker never runs on store traffic

The DNS records for your store must be Proxied (orange cloud) in Cloudflare. Worker routes only run on traffic that passes through Cloudflare's proxy.

Your domain redirects to the myshopify.com address

In Shopify admin open Settings → Domains and make sure your custom domain is set as the primary domain. Shopify redirects non-primary domains to the primary one.

Crawlers are blocked before they reach the Worker

Bot Fight Mode and AI crawler blocking (Security → Bots) run before Worker routes. Allow the crawlers you want pre-rendered.

Crawler requests return errors instead of rendered HTML

Confirm the Worker has a secret named LOVABLEHTML_API_KEY with a valid API key from your Encited dashboard, then redeploy the Worker.

Request and security behavior#

  • Only crawler GET requests for public HTML pages are answered with pre-rendered snapshots.
  • Along with the public page URL, only the API key and crawler-classification headers are sent to Encited.
  • Cookie and Authorization headers are never forwarded to Encited.
  • If Encited is unavailable or does not return rendered HTML, the request continues to Shopify unchanged.

If your zone runs bot protection or rate limiting, those rules block us before the Worker route helps. Set a render token and add the Cloudflare rule so renders and audit crawls get through.

Common questions#

Does this touch checkout or the Shopify admin?#

No. Only crawler requests for HTML pages are answered with pre-rendered snapshots. Checkout, admin, apps, and all browser traffic continue to Shopify unchanged.

Can I use the no-code DNS setup with Shopify instead?#

Only with extra setup inside Shopify. The myshopify.com address permanently redirects to your primary domain, so it can't serve as the pre-render origin. You'd first need to connect a spare subdomain in Shopify (Settings → Domains) and change its type to Alias so it serves the store without redirecting, then use that subdomain as the project URL. The Worker route on this page needs no Shopify changes at all, which is why we recommend it.

Do I need to change my Shopify DNS records?#

No. Keep the records Shopify asks for (A on the apex, CNAME www to shops.myshopify.com). The only change is switching them to Proxied in Cloudflare and attaching the Worker route.