Skip to content

Type de ressource : Cloudflare Pages Function (Endpoint SSR Astro / API Route)
Fichier source : src/pages/api/[endpoint].ts ou functions/[path].ts
Statut : [Actif / Déprécié / En développement]

[Description concise du rîle de cette Pages Function dans l’architecture SSR du site]

  • [Objectif principal : ex. fournir un endpoint SSR pour le mode Preview Sanity]
  • [BĂ©nĂ©fice pour l’architecture : ex. exĂ©cution Edge sans infrastructure serveur dĂ©diĂ©e]
Propriété Valeur
Type [Endpoint API / Route SSR / Middleware]
Méthode HTTP [GET / POST / PUT / DELETE / Les deux]
Route publique /api/[endpoint] ou /[path]
Mode de rendu SSR (Server-Side Rendering)
Cache [Voir §7]
Authentification [Aucune / Cookie / Token / Les deux]
[RequĂȘte client]
│
▌
[Cloudflare Pages]
│ ‱ Routage vers la Pages Function
│ ‱ Injection des bindings (Env)
▌
[Pages Function]
│ ‱ Validation des entrĂ©es
│ ‱ Logique mĂ©tier
│ ‱ Interaction avec les bindings (KV, R2, Queue)
▌
[Services tiers / Storage]
│ ‱ Sanity API / CRM / R2 / KV
▌
[Réponse HTTP]
[Client]
│
▌
[Middleware Global (Worker L3)] ← SĂ©curitĂ©, CSP, cookies
│
▌
[Cloudflare Pages]
│
▌
[Pages Function (SSR)] ← Cette ressource
│
▌
[Astro SSR / Services tiers]
Composant RĂŽle Type
Pages Function Endpoint SSR Cloudflare Pages
Middleware Global Sécurité + Preview cookie Worker L3
Bindings Cloudflare KV / R2 / Queue Services Cloudflare
Astro SSR Rendu des pages dynamiques Framework
# Option 1 : Routes API Astro (recommandé)
src/pages/
├── api/
│ ├── preview.ts # Activation du mode Preview
│ ├── exit-preview.ts # Sortie du mode Preview
│ ├── contact.ts # Endpoint formulaire
│ └── [autre-endpoint].ts
# Option 2 : Functions directory (pour routes non-Astro)
functions/
├── api/
│ ├── webhook.ts
│ └── [autre-endpoint].ts
Variable Description Obligatoire Valeur par défaut
[Variable] [Description] [✅/❌] [Valeur]
Binding Type Nom Description
[Nom] [KV / R2 / Queue / Secret] [Binding name] [Description]
astro.config.mjs
import cloudflare from '@astrojs/cloudflare';
export default defineConfig({
output: 'hybrid', // SSG par défaut, SSR explicite
adapter: cloudflare({
platformProxy: {
enabled: true, // Facilite le développement local avec Wrangler
},
}),
});
src/pages/api/contact.ts
import type { APIContext } from 'astro';
export const prerender = false; // SSR obligatoire
export async function POST({ request, locals }: APIContext) {
try {
const body = await request.json();
// Validation des entrées
if (!body.email || !body.message) {
return new Response(JSON.stringify({ error: 'Champs manquants' }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
});
}
// Interaction avec les bindings
await locals.runtime.env.SUBMISSIONS_QUEUE.send({
email: body.email,
message: body.message,
submittedAt: new Date().toISOString(),
});
return new Response(JSON.stringify({ success: true }), {
status: 200,
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-store',
},
});
} catch (error) {
console.error('Erreur API contact:', error);
return new Response(JSON.stringify({ error: 'Erreur serveur' }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
}
src/pages/api/preview.ts
import type { APIContext } from 'astro';
export const prerender = false;
export async function GET({ request, cookies, redirect }: APIContext) {
const url = new URL(request.url);
const secret = url.searchParams.get('secret');
const slug = url.searchParams.get('slug') || '/';
// Validation du secret (déjà faite par le Middleware, mais double vérification)
if (!secret || secret !== import.meta.env.SANITY_PREVIEW_SECRET) {
return new Response('Jeton invalide', { status: 401 });
}
// Pose du cookie de preview
cookies.set('sanity-preview', 'true', {
path: '/',
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 3600, // 1 heure
});
// Redirection vers la page demandée
return redirect(slug, 307);
}
src/pages/api/exit-preview.ts
import type { APIContext } from 'astro';
export const prerender = false;
export async function GET({ cookies, redirect }: APIContext) {
// Suppression du cookie de preview
cookies.delete('sanity-preview', { path: '/' });
// Redirection vers l'accueil
return redirect('/', 302);
}
src/pages/contact.astro
---
export const prerender = false; // SSR obligatoire
import ContactForm from '../components/interactive/ContactForm';
import BaseLayout from '../layouts/BaseLayout.astro';
// AccĂšs aux bindings via locals.runtime.env
const env = Astro.locals.runtime.env;
// Logique SSR (si nécessaire)
const siteConfig = await env.KV_BINDING.get('site-config', 'json');
---
<BaseLayout title="Contact" description="Formulaire de contact">
<ContactForm apiEndpoint="/api/contact" />
</BaseLayout>
// Dans une page ou endpoint Astro
export async function GET({ locals }: APIContext) {
const env = locals.runtime.env;
// AccĂšs KV
const value = await env.KV_BINDING.get('key');
// AccĂšs R2
const object = await env.R2_BUCKET.get('path/to/file');
// AccĂšs Queue
await env.QUEUE_BINDING.send({ data: 'value' });
// AccĂšs Secret
const secret = env.SECRET_NAME;
return new Response('OK');
}
functions/api/webhook.ts
export interface Env {
KV_BINDING: KVNamespace;
R2_BUCKET: R2Bucket;
QUEUE_BINDING: Queue;
SECRET_NAME: string;
}
export const onRequestPost: PagesFunction<Env> = async (context) => {
const { env, request } = context;
// Utilisation des bindings
await env.KV_BINDING.put('key', 'value');
return new Response('OK');
};
  • ✅ Valider tous les paramĂštres d’URL et query strings
  • ✅ Valider les corps de requĂȘte (JSON, FormData)
  • ✅ Utiliser des schĂ©mas de validation (Zod, Yup)
  • ✅ Sanitiser les entrĂ©es avant traitement
// Vérification du token CSRF (si applicable)
const csrfToken = request.headers.get('x-csrf-token');
if (!csrfToken || csrfToken !== expectedToken) {
return new Response('CSRF token invalide', { status: 403 });
}
// Rate limiting simple via KV (si nécessaire)
const ip = request.headers.get('cf-connecting-ip');
const rateLimitKey = `rate-limit:${ip}`;
const currentCount = await env.KV_BINDING.get(rateLimitKey, 'json') || 0;
if (currentCount >= 10) { // 10 requĂȘtes max
return new Response('Too Many Requests', { status: 429 });
}
await env.KV_BINDING.put(rateLimitKey, currentCount + 1, { expirationTtl: 60 });
  • ✅ Ne jamais exposer les secrets dans les rĂ©ponses
  • ✅ Valider cryptographiquement les webhooks (HMAC)
  • ✅ Utiliser des comparaisons Ă  temps constant pour les secrets
  • ✅ Limiter les mĂ©thodes HTTP autorisĂ©es (GET, POST uniquement)
  • ✅ Appliquer une CSP stricte (via Middleware)
Type de route Cache-Control CDN-Cache-Control Rationale
Endpoint API (GET) public, max-age=60 max-age=300 Données semi-dynamiques
Endpoint API (POST) no-store no-store Jamais en cache
Page SSR Preview no-store, no-cache, must-revalidate, private no-store Contenu draft
Page SSR standard public, max-age=60 max-age=300, stale-while-revalidate=86400 Contenu semi-dynamique
// Dans un endpoint ou page SSR
Astro.response.headers.set('Cache-Control', 'public, max-age=60');
Astro.response.headers.set('CDN-Cache-Control', 'max-age=300, stale-while-revalidate=86400');
// Pour les réponses POST
return new Response(JSON.stringify(data), {
headers: {
'Cache-Control': 'no-store',
'Content-Type': 'application/json',
},
});
Limite Valeur StratĂ©gie d’optimisation
Mémoire RAM 128 MB Streaming, URLs présignées
CPU time 30s (Free) / 50s (Paid) Optimisation des traitements
Nombre de sous-requĂȘtes 50 Minimiser les appels externes
Taille requĂȘte/rĂ©ponse 100 MB Streaming pour gros volumes
  • ✅ Utiliser le streaming HTTP pour les gros volumes
  • ✅ DĂ©porter les uploads lourds via URLs prĂ©signĂ©es
  • ✅ Mettre en cache les donnĂ©es frĂ©quemment lues (KV)
  • ✅ Minimiser le nombre de sous-requĂȘtes
  • ✅ Utiliser stale-while-revalidate pour les pages SSR
console.log(JSON.stringify({
function: 'api-contact',
method: 'POST',
status: 'success',
duration: 123,
metadata: { email: 'user@example.com' }
}));
MĂ©trique Seuil d’alerte Canal
Taux d’erreur 5xx > 1% sur 5 min Slack
Latence p95 > 500ms Slack
Nombre de requĂȘtes > 1000/min Email
Terminal window
# Logs en temps réel
wrangler pages deployment tail --project-name=[project]
# Liste des déploiements
wrangler pages deployment list --project-name=[project]
Terminal window
# Build Astro (inclut les Pages Functions)
npm run build
# Déploiement automatique via Cloudflare Pages
# (déclenché par git push ou webhook)
Terminal window
# Via le Dashboard Cloudflare
# Cloudflare Dashboard → Pages → [Project] → Deployments → Rollback
Terminal window
# Test en local
npm run dev
# Test d'un endpoint
curl -X POST http://localhost:4321/api/contact \
-H "Content-Type: application/json" \
-d '{"email": "test@example.com", "message": "Test"}'
# Test en production
curl -X POST https://votresite.com/api/contact \
-H "Content-Type: application/json" \
-d '{"email": "test@example.com", "message": "Test"}'
SymptĂŽme Cause probable Solution
500 Internal Server Error Erreur dans le code Vérifier les logs Cloudflare
404 Not Found Route mal configurĂ©e VĂ©rifier l’emplacement du fichier
Bindings non disponibles Configuration wrangler.toml manquante Vérifier la configuration
Timeout Traitement trop long Optimiser ou déporter vers un Worker
CORS error Headers manquants Ajouter les headers CORS
Code HTTP Signification Action
400 RequĂȘte malformĂ©e Valider les entrĂ©es
401 Non authentifié Vérifier les tokens/cookies
403 AccÚs refusé Vérifier les permissions
404 Route non trouvĂ©e VĂ©rifier l’emplacement du fichier
429 Rate limit dépassé Implémenter rate limiting
500 Erreur serveur Vérifier les logs
  • ✅ PrĂ©fĂ©rer les routes API Astro (src/pages/api/) aux Functions standalone
  • ✅ Centraliser la logique mĂ©tier dans des services rĂ©utilisables
  • ✅ Valider toutes les entrĂ©es (Zod, schĂ©mas custom)
  • ✅ GĂ©rer explicitement les erreurs (try/catch)
  • ✅ Logger de maniĂšre structurĂ©e (JSON)
  • ✅ Typer strictement les bindings et les rĂ©ponses
  • ✅ Utiliser export const prerender = false pour les routes SSR
  • ✅ Retourner des rĂ©ponses JSON avec les bons headers
  • ✅ Ne jamais exposer les secrets dans les rĂ©ponses
  • ✅ Utiliser les bindings Cloudflare plutĂŽt que des appels HTTP internes
  • ✅ Valider cryptographiquement les webhooks (HMAC)
  • ✅ Utiliser des comparaisons Ă  temps constant pour les secrets
  • ✅ Appliquer une CSP stricte (via Middleware)
  • ✅ Limiter les mĂ©thodes HTTP autorisĂ©es
  • ✅ ImplĂ©menter un rate limiting si nĂ©cessaire
  • ✅ Utiliser le streaming HTTP pour les gros volumes
  • ✅ DĂ©porter les uploads lourds via URLs prĂ©signĂ©es
  • ✅ Mettre en cache les donnĂ©es frĂ©quemment lues
  • ✅ Minimiser le nombre de sous-requĂȘtes
  • ✅ Utiliser stale-while-revalidate pour les pages SSR
  • [Évolution 1 : ex. Ajout de endpoints API pour le headless CMS]
  • [Évolution 2 : ex. ImplĂ©mentation de GraphQL API]
  • [Évolution 3 : ex. Support de WebSocket pour le temps rĂ©el]
  • ❌ Les Pages Functions ont une limite de 128 MB de RAM
  • ❌ Le CPU time est limitĂ© Ă  30s (Free) / 50s (Paid)
  • ❌ Pas de support natif pour WebSocket (nĂ©cessite Workers)
  • ❌ Les bindings ne sont pas disponibles en mode SSG
  • Documentation Cloudflare Pages : Functions
  • Documentation Astro : Cloudflare Adapter
  • DAT L2 : §2.1 - Mode Hybride & Adaptateur Cloudflare
  • DAT L3 : §4 - Pages Functions Astro (SSR)
  • Pages Functions associĂ©es :
    • src/pages/api/preview.ts
    • src/pages/api/exit-preview.ts
    • src/pages/api/contact.ts
  • Fichiers concernĂ©s :
    • src/pages/api/*.ts
    • functions/**/*.ts (si applicable)
    • astro.config.mjs
Date Version Description Auteur
YYYY-MM-DD 1.0 Création initiale [Nom]
YYYY-MM-DD 1.1 [Modification] [Nom]
---
**Points clés de ce template :**
- Couvre les deux approches (routes API Astro et Functions standalone)
- Détaille l'accÚs aux bindings Cloudflare via `locals.runtime.env`
- Inclut la stratégie de cache par type de route
- Documente les limites Cloudflare et les stratégies d'optimisation
- Fournit les commandes de debug et de rollback
- Sépare clairement les bonnes pratiques par catégorie (architecture, code, sécurité, performance)
Edit page

Last updated: