Vés al contingut

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.svg no és un fitxer dins d'una carpeta img; é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.

ClassePer a quèDetall que importa
StandardEl que es llegeix sovint: el teu frontendLa de per defecte
Intelligent-TieringPatrons d'accés que no coneixesMou els objectes sol; petita quota de seguiment per objecte
Standard-IACòpies que gairebé no es llegeixenPagues per recuperar; mínim 30 dies
One Zone-IADades que pots tornar a generarUna sola zona de disponibilitat
Glacier Instant RetrievalArxiu que es consulta un cop al trimestreAccés en mil·lisegons; mínim 90 dies
Glacier Flexible RetrievalArxiu que es consulta un cop l'anyRecuperar triga de minuts a hores
Glacier Deep ArchiveEl que guardes per obligació legalRecuperar 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 el Cache-Control que 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ò​

ConceptePreu aproximatNivell 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 Europa1 TB al mes
CloudFront (peticions HTTPS)~0,012 $ per 10.00010 milions al mes
Invalidacions0,005 $ per ruta1.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​

  • AccessDenied en XML a la URL de CloudFront. O falta la política del bucket, o l'AWS:SourceArn no é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 un max-age llarg. Comprova-ho amb curl -I i mira la capçalera cache-control.
  • Els .js arriben com a binary/octet-stream. Has pujat els fitxers amb un script que no endevina el tipus de contingut. aws s3 sync ho fa bé; si fas servir l'SDK, posa ContentType tu.
  • El certificat no surt per triar a CloudFront. L'has creat a eu-west-1. Per a CloudFront, sempre a us-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):

  1. Bucket privat i build pujat amb les dues ordres de sync d'aquesta lliçó.
  2. Distribució de CloudFront amb OAC i la política del bucket amb AWS:SourceArn.
  3. Obre la URL https://…cloudfront.net i comprova que el navegador et demana permís per al micròfon. Amb el website hosting per HTTP, no te'l demanava.
  4. Canvia un text, torna a desplegar, i fes que el canvi es vegi invalidant només /index.html.
Repàs: quatre preguntes
  1. 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.
  2. 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.
  3. 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.
  4. 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.