S3 i CloudFront: servir el frontend
Un frontend compilat (React, Vue, Astro, un HTML a mà) són fitxers estàtics: un index.html, uns quants .js i .css i imatges. No necessiten cap servidor que executi res. Només algú que els guardi i algú que els serveixi ràpid.
A AWS, això són dues peces: S3 els guarda i CloudFront els serveix. El bucket queda privat i només CloudFront hi pot llegir.
Hi ha una drecera que trobaràs a tots els tutorials: activar el static website hosting del bucket i fer-lo públic. Funciona, però et deixa sense HTTPS, amb el bucket obert a internet i sense cache. A classe ho vam viure: la pàgina que gravava àudio no tenia accés al micròfon, perquè el navegador només el dona a pàgines servides per HTTPS. Amb CloudFront al davant, el problema desapareix.
S3 en cinc idees
- Bucket: el contenidor. El nom és únic a tot AWS, no només al teu compte, i no es pot canviar. Viu en una regió.
- Objecte: el fitxer, amb les seves metadades (tipus de contingut, mida, data).
- Clau: el nom complet de l'objecte.
img/logo.svgno és un fitxer dins d'una carpetaimg; és un objecte que es diu literalment així. Les «carpetes» de la consola són un invent visual a partir de les barres. - Privat per defecte. Un bucket nou té el Block Public Access activat. Deixa'l així.
- Durabilitat d'onze nous (99,999999999 %) a les classes estàndard. És la probabilitat de no perdre un objecte, no la de poder-hi accedir en tot moment: la disponibilitat és un altre número, més baix, i depèn de la classe.
Per defecte pots tenir fins a 10.000 buckets per compte (abans eren 100; ho trobaràs encara a molts apunts).
Classes d'emmagatzematge
Totes guarden igual de bé. El que canvia és quant pagues per guardar i quant per llegir.
| Classe | Per a què | Detall que importa |
|---|---|---|
| Standard | El que es llegeix sovint: el teu frontend | La de per defecte |
| Intelligent-Tiering | Patrons d'accés que no coneixes | Mou els objectes sol; petita quota de seguiment per objecte |
| Standard-IA | Còpies que gairebé no es llegeixen | Pagues per recuperar; mínim 30 dies |
| One Zone-IA | Dades que pots tornar a generar | Una sola zona de disponibilitat |
| Glacier Instant Retrieval | Arxiu que es consulta un cop al trimestre | Accés en mil·lisegons; mínim 90 dies |
| Glacier Flexible Retrieval | Arxiu que es consulta un cop l'any | Recuperar triga de minuts a hores |
| Glacier Deep Archive | El que guardes per obligació legal | Recuperar triga hores; mínim 180 dies |
Per a un frontend, Standard i prou. Les altres són per a còpies de seguretat i arxius, i les mous amb una regla de cicle de vida (per exemple: «després de 90 dies, a Glacier»), no a mà.
Dues funcions més que convé conèixer:
- Control de versions. Cada sobreescriptura guarda la versió anterior. Salva vides quan algú esborra el que no tocava, però pagues totes les versions: combina'l amb una regla que esborri les versions antigues.
- Notificacions d'esdeveniments. Quan entra un objecte, S3 pot invocar una Lambda. És el motor de la pràctica de traducció de veu de classe: pujar un àudio dispara la transcripció, que dispara la traducció, que dispara la veu.
Pujar el frontend
BUCKET=web-$(aws sts get-caller-identity --query Account --output text)
REGIO=eu-west-1
aws s3api create-bucket --bucket "$BUCKET" --region $REGIO \
--create-bucket-configuration LocationConstraint=$REGIO
Posar el número de compte al nom és un truc per no topar amb el nom d'algú altre. El bucket neix privat; no toquis el Block Public Access.
Ara el build. La clau és separar la cache: els fitxers amb hash al nom (assets/index-4f9a1c.js) no canvien mai i es poden guardar un any; l'index.html canvia a cada desplegament i no s'ha de guardar gens.
npm run build
# Tot menys l'HTML: un any de cache, i esborra el que ja no existeix.
aws s3 sync dist/ "s3://$BUCKET/" --delete \
--exclude "*.html" \
--cache-control "public,max-age=31536000,immutable"
# L'HTML: que el navegador pregunti sempre.
aws s3 sync dist/ "s3://$BUCKET/" \
--exclude "*" --include "*.html" \
--cache-control "no-cache"
--delete esborra del bucket el que ja no és al build. Sense, els fitxers vells s'hi van acumulant per sempre.
CloudFront amb Origin Access Control
CloudFront és la xarxa de distribució d'AWS: centenars de punts de presència arreu del món que guarden una còpia dels teus fitxers a prop de qui els demana. Et dona HTTPS, compressió i cache sense fer res.
Primer, l'Origin Access Control (OAC): la identitat amb què CloudFront signarà les peticions a S3.
OAC_ID=$(aws cloudfront create-origin-access-control \
--origin-access-control-config \
Name=web-oac,SigningProtocol=sigv4,SigningBehavior=always,OriginAccessControlOriginType=s3 \
--query OriginAccessControl.Id --output text)
Després, la distribució. La configuració és un JSON:
{
"CallerReference": "web-2026-10-21",
"Comment": "frontend",
"Enabled": true,
"DefaultRootObject": "index.html",
"Origins": {
"Quantity": 1,
"Items": [{
"Id": "s3-web",
"DomainName": "NOM-DEL-BUCKET.s3.eu-west-1.amazonaws.com",
"OriginAccessControlId": "ID-DE-L-OAC",
"S3OriginConfig": { "OriginAccessIdentity": "" }
}]
},
"DefaultCacheBehavior": {
"TargetOriginId": "s3-web",
"ViewerProtocolPolicy": "redirect-to-https",
"CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6",
"Compress": true
},
"CustomErrorResponses": {
"Quantity": 1,
"Items": [{
"ErrorCode": 403,
"ResponsePagePath": "/index.html",
"ResponseCode": "200",
"ErrorCachingMinTTL": 10
}]
}
}
DIST_ARN=$(aws cloudfront create-distribution \
--distribution-config file://distribucio.json \
--query Distribution.ARN --output text)
Tres detalls d'aquest JSON:
- El
DomainNameés l'adreça REST del bucket (bucket.s3.regio.amazonaws.com), no la del website hosting. L'OAC només funciona amb la REST. 658327ea-…és la política de cache gestionada CachingOptimized: respecta elCache-Controlque has posat en pujar els fitxers.- La resposta d'error converteix els 403 en l'
index.html. Ho necessites si el frontend té rutes pròpies (/perfil,/llista): S3 no té cap fitxer que es digui així i, com que CloudFront no té permís per llistar el bucket, respon 403 en lloc de 404.
Falta l'última peça: el bucket ha de dir que només aquesta distribució hi pot llegir.
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "NomesLaDistribucio",
"Effect": "Allow",
"Principal": { "Service": "cloudfront.amazonaws.com" },
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::NOM-DEL-BUCKET/*",
"Condition": {
"StringEquals": { "AWS:SourceArn": "ARN-DE-LA-DISTRIBUCIO" }
}
}]
}
aws s3api put-bucket-policy --bucket "$BUCKET" --policy file://politica-bucket.json
Aquesta és una política de recursos: viu al bucket, no a cap usuari. La condició és el que importa: sense, qualsevol distribució de CloudFront de qualsevol compte podria llegir el teu bucket. A la lliçó 06 hi tornem.
La distribució triga uns minuts a desplegar-se. Després, la teva web és a https://dxxxxxxxx.cloudfront.net, amb HTTPS, i el bucket continua tancat.
Desplegar de nou: la invalidació
Quan tornes a fer sync, CloudFront encara té la versió anterior a la cache dels seus punts de presència. Els fitxers amb hash no són problema (tenen nom nou), però l'index.html sí.
aws cloudfront create-invalidation --distribution-id ID-DE-LA-DISTRIBUCIO --paths "/index.html"
Invalida només el que canvia de nom igual. Les primeres 1.000 rutes invalidades cada mes són gratuïtes; un "/*" compta com una sola ruta, però buida la cache sencera i durant uns minuts tot torna a anar a S3.
Per posar-hi un domini propi necessites un certificat d'ACM a la regió us-east-1, sigui on sigui el bucket. És l'excepció que enganxa tothom el primer cop.
I si no vols muntar-ho tu
Amplify Hosting fa tot això per sota (S3, CloudFront, certificat i invalidació) i ho connecta a un repositori: fas git push i en uns minuts és publicat, amb una preview per branca. És el que faig servir per a les webs dels clients.
Muntar-ho a mà almenys un cop val la pena igualment: quan Amplify falli, sabràs què hi ha a sota.
Què costa això
| Concepte | Preu aproximat | Nivell gratuït always free |
|---|---|---|
| S3 Standard (emmagatzematge) | ~0,023 $ per GB i mes | — |
| S3 (peticions GET) | ~0,0004 $ per 1.000 | — |
| CloudFront (transferència cap a internet) | ~0,085 $ per GB a Europa | 1 TB al mes |
| CloudFront (peticions HTTPS) | ~0,012 $ per 10.000 | 10 milions al mes |
| Invalidacions | 0,005 $ per ruta | 1.000 rutes al mes |
Preus de l'octubre de 2026, en dòlars. La transferència de S3 cap a CloudFront no es paga.
Un frontend de 5 MB amb 20.000 visites al mes són uns 100 GB de transferència: dins del terabyte gratuït de CloudFront. Pagaràs cèntims d'emmagatzematge. Amb tràfic de debò, el terabyte gratuït de CloudFront és deu vegades el que AWS regala de sortida directa des de S3 (100 GB al mes): per una vegada, la manera segura és també la barata.
Errors habituals
AccessDenieden XML a la URL de CloudFront. O falta la política del bucket, o l'AWS:SourceArnno és el de la distribució, o has posat l'adreça del website hosting com a origen. Si passa només amb una ruta, és que el fitxer no existeix: recorda que S3 diu 403 quan no troba res.- Has desplegat i continues veient la versió vella. Falta la invalidació de l'
index.html, o l'has pujat amb unmax-agellarg. Comprova-ho ambcurl -Ii mira la capçaleracache-control. - Els
.jsarriben com abinary/octet-stream. Has pujat els fitxers amb un script que no endevina el tipus de contingut.aws s3 syncho fa bé; si fas servir l'SDK, posaContentTypetu. - El certificat no surt per triar a CloudFront. L'has creat a
eu-west-1. Per a CloudFront, sempre aus-east-1. - Un bucket públic «només per provar». Els bots que busquen buckets oberts el troben en hores. No en necessites cap mai de públic per servir una web.
Exercici
Publica el frontend de la pràctica de traducció de veu (el que grava àudio i el puja):
- Bucket privat i build pujat amb les dues ordres de
syncd'aquesta lliçó. - Distribució de CloudFront amb OAC i la política del bucket amb
AWS:SourceArn. - Obre la URL
https://…cloudfront.neti comprova que el navegador et demana permís per al micròfon. Amb el website hosting per HTTP, no te'l demanava. - Canvia un text, torna a desplegar, i fes que el canvi es vegi invalidant només
/index.html.
Repàs: quatre preguntes
- Què vol dir que S3 tingui onze nous de durabilitat? Que la probabilitat de perdre un objecte és ínfima. No diu res de la disponibilitat, que és un altre número.
- Per què l'origen de CloudFront ha de ser l'adreça REST del bucket i no la del website? Perquè l'Origin Access Control només signa peticions a l'adreça REST.
- Per què la resposta d'error de la SPA és per al 403 i no per al 404? Perquè sense permís per llistar el bucket, S3 respon 403 quan un objecte no existeix.
- En quina regió va el certificat d'un domini propi a CloudFront? A
us-east-1, sempre.
La IA a l'empresa, amb criteri
Cada setmana, una sola cosa que pots fer servir: la notícia que toca la teva empresa i què en faria jo, una pregunta de client, un cas real o un exercici de quinze minuts. Un correu per setmana com a molt, i te'n pots donar de baixa amb un clic.