Tutorial
15 min read
43 views

Ephem-Cache : Mise en cache en périphérie pour prévisualisations locales lourdes avec Cloudflare Workers et Quick Tunnels

Éliminez le retard de prévisualisation localhost avec Ephem-Cache. Mettez en cache en périphérie les assets statiques lourds via Cloudflare Workers tout en tunnelant les requêtes dynamiques vers votre machine.

IT
InstaTunnel Team
Published by the InstaTunnel team | Editorial policy
Ephem-Cache : Mise en cache en périphérie pour prévisualisations locales lourdes avec Cloudflare Workers et Quick Tunnels

Quick answer

Ephem-Cache : Accélérez les prévisualisations localhost avec Cloudflare: quick comparison answer

Choose the tunnel tool based on the network model: public HTTPS URLs for webhooks and demos, private mesh access for internal apps, and managed infrastructure when policy controls matter most.

Which tunnel tool is best for public webhook testing?

Use a public HTTPS localhost tunnel with stable URLs. InstaTunnel focuses on webhook testing, demos, OAuth callbacks, and MCP endpoint workflows.

When should I choose a private network tool instead?

Choose a private mesh or Zero Trust tool when every user and service should stay inside a controlled private network.

Introduction : Le goulet d’étranglement de la boucle de rétroaction du frontend distant

Vous terminez une page d’atterrissage riche en médias. Elle comporte des images WebP haute résolution, une vidéo de fond en boucle et quelques assets 3D. Tout cela semble instantané sur votre ordinateur portable. Vous lancez votre serveur de développement (Vite, Next.js ou autre) et souhaitez qu’un designer distant, un chef de produit ou un client le revoie en direct.

La solution habituelle est un tunnel. Des outils comme ngrok, Localtunnel et Cloudflare Tunnel (cloudflared) exposent un port local via une URL publique. Cela fonctionne bien pour le HTML et les appels API. Pour les médias lourds, cela présente une faiblesse structurelle : chaque octet de chaque asset quitte votre machine via votre lien d’upload domestique ou de bureau, une fois par spectateur, à chaque rechargement.

  • L’upload est le goulot d’étranglement. Si trois relecteurs ouvrent le lien simultanément, ils se concurrencent sur le même tuyau en amont. Une vidéo de 45 Mo qui se lit en douceur depuis votre SSD se bloque lorsqu’elle doit traverser votre lien en amont trois fois.
  • La prévisualisation ne reflète pas la production. Les vidéos bloquées et les images lentes donnent une impression pire que le site déployé, vous recevez donc des retours sur votre vitesse d’upload plutôt que sur votre design.

Ephem-Cache est le nom que je donne à un petit pattern qui corrige cela. Un Cloudflare Worker se place devant votre tunnel. Il permet à Cloudflare de mettre en cache les médias statiques lourds après la première récupération, et il transfère tout ce qui est dynamique (HTML, API, WebSockets pour le rechargement à chaud) directement vers votre machine.

Ce guide couvre l’architecture, un Worker fonctionnel, les réglages du dev-server qui sinon cassent la configuration, ainsi que les limites et compromis de sécurité à connaître. Les détails ici ont été vérifiés avec la documentation actuelle de Cloudflare au 14 octobre 2026. J’ai délibérément omis les chiffres de benchmark, car les résultats dépendent de votre lien en amont, de vos fichiers et de la localisation de vos relecteurs. La section de mesure montre comment obtenir les vôtres.

1. L’architecture

[ Relecteur distant ]
        │
        ▼
[ Edge Cloudflare : Worker avec mise en cache des Workers activée ]
        │
        ├── Cache HIT ──► servi depuis le cache de Cloudflare, Worker non exécuté
        │
        └── Cache MISS / requête dynamique
                │
                ▼
        Worker proxy vers l’URL du Quick Tunnel (trycloudflare.com)
                │
                ▼
        cloudflared ──► http://localhost:5173

Les règles de routage sont simples :

  • Médias cacheables (images, vidéos, polices, assets 3D) renvoient un en-tête Cache-Control permettant à Cloudflare de les stocker. Après la première récupération, les requêtes suivantes peuvent être répondues sans impliquer votre machine.
  • Tout le reste (HTML, routes API, WebSockets HMR, pages rendues côté serveur) est marqué no-store et transmis à chaque requête.

Pourquoi la mise en cache des Workers plutôt que l’API Cache

Les versions antérieures de ce pattern utilisaient caches.default (l’API Cache) dans le Worker. La documentation de Cloudflare décrit désormais une fonctionnalité séparée, Workers Caching, qui convient mieux à ce cas :

API Cache (caches.default) Workers Caching
Portée Locale à un seul centre de données. Le contenu ne se réplique pas ailleurs. Hiérarchique : cache de niveau inférieur près du visiteur, plus un cache supérieur qui agrège les remplissages à travers le réseau.
Où ça fonctionne Fonctionne sur domaines personnalisés (et Fonctions Pages). Suit le Worker, donc fonctionne sur workers.dev, routes, domaines personnalisés et liaisons de service.
Requêtes simultanées manquées Non fusionnées. Une surcharge peut invoquer votre Worker une fois par requête. Fusionnées : plusieurs requêtes simultanées pour une clé invoquent le Worker une fois par centre de données.
Contenu partiel cache.put échoue sur une réponse 206, vous devez stocker la réponse complète vous-même. Cloudflare supprime Range, stocke le 200 complet, et découpe les plages lui-même.
Purge cache.delete est local à un centre de données. La purge globale passe par la purge de zone. ctx.cache.purge() depuis le Worker, avec les options de la section 5.

Ces différences comptent ici. La fusion des requêtes empêche trois relecteurs de déclencher trois copies d’un upload de 45 Mo lors du premier chargement. La gestion native des plages est importante pour la lecture vidéo <video>. Ne pas nécessiter de domaine personnalisé simplifie la configuration.

Workers Caching nécessite Wrangler 4.69.0 ou ultérieur.

2. Configuration

Étape 1 : Démarrer le serveur de développement et un Quick Tunnel

Lancez votre serveur de dev (le port par défaut de Vite est 5173), puis ouvrez un Quick Tunnel dans un autre terminal :

cloudflared tunnel --url http://localhost:5173

cloudflared affiche une URL aléatoire https://<mots-aléatoires>.trycloudflare.com. Aucun compte Cloudflare ni domaine n’est nécessaire pour cette étape.

Connaissez les limites des Quick Tunnels avant de vous y appuyer. Cloudflare en décrit les caractéristiques :

  • Chaque tunnel gère jusqu’à 200 requêtes en cours. Au-delà, une requête reçoit un 429 HTTP.
  • Les événements Server-Sent ne sont pas supportés. Les WebSockets ne sont pas listés parmi les fonctionnalités non supportées.
  • Il n’y a aucun SLA ni garantie de disponibilité, et l’URL change à chaque redémarrage de cloudflared.
  • Les Quick Tunnels ne sont pas supportés si un config.yaml existe dans votre répertoire .cloudflared.

Le plafond de 200 requêtes est une raison supplémentaire de mettre en cache. Les hits de cache ne traversent jamais le tunnel.

Étape 2 : Corriger la vérification d’hôte de votre serveur de dev

Cette étape est facile à manquer, et c’est la raison la plus courante pour qu’une page blanche s’affiche. Lorsque le Worker transfère une requête, l’en-tête Host est le nom d’hôte du tunnel, pas localhost. Les serveurs de dev modernes rejettent les hôtes inconnus.

Vite affiche Blocked request. This host is not allowed. Ajoutez le domaine du tunnel dans vite.config.ts :

import { defineConfig } from "vite";

export default defineConfig({
  server: {
    // Un point en tête permet tous les sous-domaines de trycloudflare.com.
    allowedHosts: [".trycloudflare.com"],
  },
});

Évitez allowedHosts: true. Cela désactive totalement la vérification d’hôte, exposant le serveur de dev aux attaques de type DNS-rebinding. En alternative, cloudflared peut réécrire l’en-tête avec --http-host-header localhost:5173.

Next.js limite ses points de terminaison en mode dev, y compris le WebSocket HMR à /_next/webpack-hmr, aux requêtes de même origine. Ajoutez le nom d’hôte que vos relecteurs utiliseront (le nom d’hôte de votre Worker) dans allowedDevOrigins dans next.config.js. Mettez également à jour vers Next.js 16.1.7 ou ultérieur. La CVE-2026-27977 décrit une faille dans les versions 16.0.1 à 16.1.6 où next dev pouvait accepter des connexions WebSocket avec Origin: null même si allowedDevOrigins était configuré. Étant donné que vous mettez un serveur de dev sur Internet, vous souhaitez cette correction.

Étape 3 : Créer le projet Worker

npm create cloudflare@latest ephem-cache-worker
cd ephem-cache-worker

Cloudflare recommande désormais wrangler.jsonc plutôt que wrangler.toml pour les nouveaux projets. Utilisez cette configuration :

{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "ephem-cache-preview",
  "main": "src/index.ts",
  "compatibility_date": "2026-10-14",
  "cache": { "enabled": true },
  "vars": {
    "LOCAL_ORIGIN": "https://replace-me.trycloudflare.com"
  }
}

cache.enabled force Cloudflare à vérifier son cache avant d’exécuter votre Worker. La compatibility_date correspond à la date du jour, comme le suggèrent les exemples de Cloudflare.

Étape 4 : Le Worker

Remplacez src/index.ts par le code suivant. Il a été vérifié syntaxiquement, mais testez-le dans votre projet avant de vous y fier.

export interface Env {
  LOCAL_ORIGIN: string; // ex. "https://mots-aléatoires.trycloudflare.com"
  PURGE_TOKEN?: string; // secret optionnel pour le point de purge
}

// Assets lourds, peu changeants, à mettre en cache lors d’une revue.
// JS et CSS sont délibérément exclus (voir section 4).
const MEDIA =
  /\.(png|jpe?g|gif|webp|avif|svg|ico|mp4|webm|mov|woff2?|ttf|otf|glb|gltf|ktx2|hdr)$/i;

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): PromisecResponsee {
    const url = new URL(request.url);

    // Optionnel : purge du cache de ce Worker à la demande.
    if (url.pathname === "/__ephem/purge"  request.method === "POST") {
      if (!env.PURGE_TOKEN || request.headers.get("x-purge-token") !== env.PURGE_TOKEN) {
        return new Response("Forbidden", { status: 403, headers: { "Cache-Control": "no-store" } });
      }
      const result = await ctx.cache.purge({ purgeEverything: true });
      return new Response(result.success ? "Purged" : "Échec de la purge", {
        status: result.success ? 200 : 500,
        headers: { "Cache-Control": "no-store" },
      });
    }

    const isUpgrade = request.headers.get("Upgrade")?.toLowerCase() === "websocket";
    const isCacheableMedia =
      request.method === "GET"  !isUpgrade  MEDIA.test(url.pathname);

    // Repointage de la requête vers le tunnel.
    const target = new URL(request.url);
    const origin = new URL(env.LOCAL_ORIGIN);
    target.protocol = origin.protocol;
    target.host = origin.host;

    const upstreamReq = new Request(target, request);

    if (isCacheableMedia) {
      // Toujours demander le fichier complet à votre machine pour que le cache stocke une
      // réponse 200 complète, jamais un 304 ou une réponse partielle.
      upstreamReq.headers.delete("If-None-Match");
      upstreamReq.headers.delete("If-Modified-Since");
      upstreamReq.headers.delete("Range");
    }

    let upstream: Response;
    try {
      upstream = await fetch(upstreamReq);
    } catch {
      return new Response("Ephem-Cache : impossible d’atteindre le tunnel local.", {
        status: 502,
        headers: { "Content-Type": "text/plain", "Cache-Control": "no-store" },
      });
    }

    // WebSocket (HMR) passe directement.
    if (isUpgrade || upstream.status === 101) return upstream;

    const res = new Response(upstream.body, upstream);
    if (isCacheableMedia  upstream.status === 200) {
      res.headers.set("Cache-Control", "public, max-age=3600");
    } else {
      res.headers.set("Cache-Control", "no-store");
    }
    return res;
  },
} satisfies ExportedHandlercEnve;

Voici quelques décisions de conception à expliquer :

Toute réponse non-média est explicitement no-store. Avec la mise en cache des Workers activée, une réponse 200 sans en-tête Cache-Control est toujours mise en cache selon l’heuristique de fraîcheur RFC 9111, pour 2 heures par défaut. Sans no-store explicite, vos réponses HTML et API pourraient être servies périmées depuis le cache.

Les WebSockets sont renvoyés intacts. Cloudflare indique que les requêtes de mise à niveau WebSocket bypassent la mise en cache des Workers et invoquent toujours votre Worker. Renvoyer la réponse de mise à niveau inchangée permet de continuer le rechargement à chaud.

Les en-têtes conditionnels et Range sont supprimés sur le chemin média. Les serveurs de dev répondent aux requêtes conditionnelles avec 304, ce qui n’est pas une réponse avec corps à mettre en cache. Cloudflare supprime aussi Range avant d’invoquer le Worker et renvoie la tranche demandée depuis la réponse complète en cache, évitant au Worker d’implémenter la gestion des byte ranges. Si un Worker renvoie son propre 206, Cloudflare ne le stockera pas.

Le Worker reste léger. Il ne fait que transmettre les requêtes et définir des en-têtes. Les plans gratuits et payants offrent des sous-requêtes et du temps CPU pour cela.

Étape 5 : Déployer avec l’URL du tunnel actuel

L’URL du tunnel change à chaque session, il faut donc la passer lors du déploiement plutôt que de modifier le fichier :

npx wrangler secret put PURGE_TOKEN        # optionnel
npx wrangler deploy --var LOCAL_ORIGIN:https://votre-tunnel- actuel.trycloudflare.com

wrangler deploy accepte --var clé:valeur en alternative à la section vars de votre configuration, et la valeur du flag l’emporte si les deux sont définis. Un effet secondaire utile : par défaut, la version du Worker fait partie de la clé du cache, donc chaque déploiement démarre avec un cache froid. Une nouvelle session de revue ne hérite jamais des assets d’une précédente.

Encapsulez ces deux commandes dans un script de package pour qu’une nouvelle session ne nécessite qu’une seule commande.

3. Comportement du cache

Connaître la clé du cache et les règles de bypass évite des heures de confusion :

  • La clé est le point d’entrée, le chemin plus la chaîne de requête, et (par défaut) la version du Worker. Le host n’en fait pas partie. ?a=1b=2 et ?b=2a=1 sont des clés différentes. Les en-têtes de requête comme User-Agent et Cookie ne partitionnent pas le cache sauf si vous définissez Vary.
  • Seuls GET et HEAD sont mis en cache. Les autres méthodes atteignent toujours votre Worker.
  • Le bypass automatique s’applique. Une réponse avec Set-Cookie, une requête avec un en-tête Authorization (sauf si la réponse est explicitement public), ou une réponse marquée private ou no-store ne sera pas mise en cache normalement.
  • Cf-Cache-Status indique ce qui s’est passé. MISS signifie que votre Worker a été exécuté et la réponse stockée. HIT indique que Cloudflare a répondu sans exécuter votre Worker. BYPASS indique que la mise en cache a été ignorée.
  • La première visualisation n’est pas plus rapide. Une requête à froid pour une grande vidéo doit parcourir tout le chemin depuis votre machine. La mise en cache des Workers stocke la réponse complète, donc le premier visiteur paie le coût du fichier entier. Faites chauffer le cache vous-même en ouvrant la page une fois avant la revue.
  • Les limites de taille s’appliquent. La page des limites des Workers indique que celles du CDN s’appliquent : 512 Mo par objet sur les plans Free, Pro et Business, et 5 Go sur Enterprise. Les assets plus gros ne seront pas mis en cache.

4. Invalidation : Quoi mettre en cache, et pour combien de temps

Les fichiers locaux changent constamment, donc le principal risque est la mise en cache d’assets périmés. La stratégie :

  1. Mettre en cache les médias, pas le code. Les fichiers source dans un serveur de dev gardent des noms stables tandis que leur contenu change. Vite gère cela en rendant les requêtes de modules sources conditionnelles (304), tandis que les dépendances pré-assemblées ont max-age=31536000,immutable dans le navigateur. Mettre .js et .css derrière un cache périphérique d’une heure ajouterait surtout de la confusion, donc l’expression régulière ci-dessus les exclut.
  2. Garder le TTL court. Une heure est un point de départ. Réduisez max-age si les designers échangent des images pendant la revue.
  3. Purger à la demande. Le point de terminaison optionnel dans le Worker appelle ctx.cache.purge({ purgeEverything: true }) : curl -X POST https://<votre-hôte-worker>/__ephem/purge -H "x-purge-token: $PURGE_TOKEN"

purge accepte aussi tags et pathPrefixes. La mise en cache des Workers est privée à votre Worker, et les purges de zone depuis le tableau de bord ou l’API ne l’affectent pas. Les purges utilisent les limites de taux du plan Free. 4. Redéployer pour réinitialiser. La version du Worker étant partie intégrante de la clé, wrangler deploy est lui-même une invalidation complète. 5. Prévisualiser une version de production pour les revues finales. Si vous exécutez vite build et servez la sortie avec vite preview, les noms de fichiers d’assets portent des hash de contenu, donc les fichiers modifiés obtiennent automatiquement de nouvelles URLs. À ce stade, vous pouvez ajouter en toute sécurité js|css|mjs à l’expression régulière.

5. Mesurer le résultat vous-même

Plutôt que de citer des chiffres dépendant d’une configuration spécifique, mesurez les vôtres :

# 1. Première requête : attendre Cf-Cache-Status: MISS
curl -sI https://<votre-worker>/media/hero.mp4 | grep -i cf-cache-status

# 2. Deuxième requête : attendre HIT
curl -sI https://<votre-worker>/media/hero.mp4 | grep -i cf-cache-status

# 3. Comparer le temps de téléchargement avec l’URL du tunnel directement
curl -s -o /dev/null -w "worker: %{time_total}s  %{size_download} bytes\n" https://<votre-worker>/media/hero.mp4
curl -s -o /dev/null -w "tunnel: %{time_total}s  %{size_download} bytes\n" https://<votre-tunnel>.trycloudflare.com/media/hero.mp4

Pour une image réaliste, faites la même comparaison depuis une machine dans la région de vos relecteurs, ou demandez à un relecteur d’ouvrir l’onglet Réseau dans DevTools et de partager un fichier HAR. Surveillez votre lien en amont pendant que deux ou trois personnes chargent la page. La version en cache doit la laisser presque inerte après le premier chargement.

6. Limites, termes et coûts

  • Plan Gratuit Workers : 100 000 requêtes par jour, 10 ms de CPU par requête et 50 sous-requêtes par requête. Un proxy en streaming utilise peu de CPU. Mais avec la mise en cache activée, chaque requête vers le Worker, y compris les hits, est facturée au tarif standard.
  • Conditions de Cloudflare sur les gros fichiers. Le blog de Cloudflare explique que ses conditions ont changé pour que la vidéo et autres gros fichiers puissent être servis via son CDN lorsqu’ils sont hébergés par des services Cloudflare comme Stream, Images ou R2. Les vidéos et gros fichiers hébergés en dehors de Cloudflare restent limités sur le CDN. Un tunnel vers votre ordinateur portable est une origine externe. Vérifiez les conditions spécifiques de Cloudflare pour les fichiers volumineux avant de router de grandes vidéos via cette configuration, et limitez-la à de courtes sessions de revue. Pour du média permanent, hébergez les fichiers dans R2.
  • Pas de SLA. Quick Tunnels sont un outil de test. Pour tout ce dont vous dépendez, utilisez un Tunnel Cloudflare nommé.

7. Considérations de sécurité

Ce pattern expose un site non publié sur Internet public, donc quelques points méritent attention :

  • Les hits en cache évitent votre Worker. Toute vérification que vous écrivez dans le Worker (un token, un mot de passe) ne s’exécute qu’en cas de cache miss. Une fois un fichier média en cache, quiconque connaît son URL peut le récupérer. N’utilisez pas ce pattern pour des assets confidentiels ou embargoés.
  • L’URL du tunnel est aussi publique. Quiconque connaît l’adresse aléatoire trycloudflare.com peut accéder directement à votre serveur de dev, en contournant le Worker. La documentation de Cloudflare décrit une option --allowed-mail pour Quick Tunnels qui oblige les visiteurs à entrer un PIN à usage unique. Cette porte pourrait aussi faire échouer les requêtes du Worker, donc elle ne se combine pas avec cette architecture. Une alternative pratique consiste à faire ajouter par le Worker un en-tête secret à ses requêtes en amont et rejeter celles qui ne le portent pas, via un petit plugin Vite : // vite.config.ts (extrait) const guard = { name: "ephem-guard", configureServer(server) { server.middlewares.use((req, res, next) => { if (req.headers["x-ephem-secret"] !== process.env.EPHEM_SECRET) { res.statusCode = 403; return res.end("Forbidden"); } next(); }); }, };

Ce qui ne couvre que les requêtes HTTP simples. Gardez le secret hors de votre dépôt.

  • Ne pas divulguer de secrets dans les bundles. Tout ce qui est côté client, y compris les variables d’environnement intégrées par votre framework, finit dans des fichiers que les visiteurs peuvent lire. Gardez les URLs de bases de données et clés API privées côté serveur.
  • Narrow l’autorisation des hôtes. Utilisez .trycloudflare.com plutôt que de désactiver la vérification d’hôte.
  • Démolissez après la revue. Arrêtez cloudflared, supprimez le Worker (npx wrangler delete) ou au moins purgez son cache, et faites tourner à nouveau PURGE_TOKEN si vous l’avez partagé.

Conclusion

Un tunnel local est la façon la plus rapide de montrer un travail non terminé, mais il fait de votre connexion d’upload le réseau de livraison pour chaque image et vidéo. Mettre en place un petit Worker Cloudflare avec la mise en cache des Workers devant le tunnel déplace le trafic répété pour les médias lourds dans le cache de Cloudflare et laisse votre machine servir uniquement la première requête pour chaque fichier, plus le trafic dynamique qui en découle.

Ce pattern a de véritables limites : la première visualisation reste lente, les fichiers en cache sont effectivement publics, Quick Tunnels n’offrent aucune garantie de disponibilité, et les termes de Cloudflare limitent les fichiers volumineux hébergés en externe sur leur CDN. Dans ces limites, il facilite grandement les revues à distance de pages riches en médias, en étant beaucoup moins dépendant de votre vitesse d’upload. Commencez par une session courte, vérifiez Cf-Cache-Status sur quelques assets, et mesurez la différence avec vos propres fichiers.

Sources

Continue from this article into the most relevant product guides and workflows.

Related Topics

#edge caching#Cloudflare Workers#local tunnels#localhost preview#static asset caching#edge computing#frontend developer tools#remote stakeholder review#reduce preview latency#media-heavy frontend builds#dynamic local preview#Cloudflare edge compute#local development workflow#reverse proxy for localhost#tunneling localhost#Cloudflare Workers KV#edge caching static assets#web development optimization#share localhost with clients#remote client review tools#fast localhost previews#local server tunneling#web performance at the edge#Cloudflare KV cache#localhost debugging tools#frontend staging workflows#local API tunneling#bypass ngrok latency#fast local tunneling#edge caching dynamic content#serverless edge caching#remote testing local builds#Cloudflare Workers tutorial#edge caching strategies#optimize local development#local web server sharing#instant localhost sharing#frontend deployment preview#secure local tunneling#high performance local preview#edge worker caching#cloudflare workers static assets#local development speed up#developer productivity tools#low latency local preview#caching static files at edge#proxying dynamic requests localhost#web app testing tools#client feedback workflow tools#modern frontend tooling#ephemeral edge cache#cloudflare edge worker proxy

Keep building with InstaTunnel

Read the docs for implementation details or compare plans before you ship.

Share this article

More InstaTunnel Insights

Discover more tutorials, tips, and updates to help you build better with localhost tunneling.

Browse All Articles