CicloUrbano funciona i està provada, però viu a localhost, amb una base de dades que és un fitxer JSON i un servidor de desenvolupament que mai no hauria d'assomar-se a internet. Aquesta lliçó cobreix l'últim tram: convertir el projecte en un paquet de fitxers estàtics, entendre què es pot i què no es pot amagar en una aplicació que s'executa al navegador d'una altra persona, resoldre el problema d'enrutament que fa que /reservas retorni un 404 tan bon punt surt de localhost, desplegar-lo, i saber què vigilar després. I com que és l'última lliçó del curs, tanca també el camí recorregut i assenyala per on continuar.

Contingut

  1. La construcció per a producció
  2. Revisar la mida abans de publicar
  3. Variables d'entorn: què és realment públic
  4. El 404 en recarregar: enrutament en client sobre un servidor estàtic
  5. Desplegament pas a pas en una plataforma estàtica
  6. L'alternativa del contenidor: Docker i Nginx
  7. L'API en producció: què falta de veritat
  8. Després del desplegament: monitorització i mètriques reals
  9. Llista de comprovació de llançament
  10. Següents passos amb aquest projecte
  11. Com continuar aprenent

  1. La construcció per a producció

Fins ara tot ha passat amb npm run dev, on Vite serveix els mòduls sense empaquetar i recarrega en calent. Això és còmode per desenvolupar i no és el que es publica. La construcció és una altra cosa:

npm run build
vite v6.0.5 building for production...
✓ 412 modules transformed.
dist/index.html                            0.48 kB │ gzip:  0.31 kB
dist/assets/index-B7dK2p9x.css            14.82 kB │ gzip:  3.41 kB
dist/assets/PaginaTaller-Qm3rT8vc.js       6.12 kB │ gzip:  2.18 kB
dist/assets/PaginaDetalleEstacion-Nx7.js   8.94 kB │ gzip:  3.02 kB
dist/assets/PaginaNuevaReserva-Zk1p.js    11.37 kB │ gzip:  3.86 kB
dist/assets/index-Yt4mR9Ka.js            268.41 kB │ gzip: 87.65 kB
✓ built in 3.42s

Què ha fet, pas a pas:

Tasca Què implica
Empaquetat Centenars de mòduls es combinen en uns pocs fitxers, per no fer centenars de peticions
Minificació S'eliminen espais, comentaris i noms llargs de variables locals
Eliminació de codi mort El que no s'importa des de cap lloc no s'inclou
Divisió en fragments Cada ruta amb lazy produeix el seu propi fitxer, com es va preparar a Divisió de Codi
Mode producció de React Desapareixen els avisos de desenvolupament, les comprovacions extra i el doble render de StrictMode
Hash en el nom index-Yt4mR9Ka.js conté una empremta del contingut

Aquest hash és la peça que més es malinterpreta. Serveix per poder dir-li al servidor «guarda aquests fitxers en memòria cau per sempre» sense condemnar l'usuari a una versió antiga: si el contingut canvia, el nom canvia, i el navegador el demana com si fos un fitxer nou. La conseqüència pràctica és una regla de memòria cau de dues velocitats:

Fitxer Capçalera de memòria cau Per què
index.html no-cache (o molt curta) És l'únic que no porta hash: és qui apunta als altres
assets/* amb hash max-age=31536000, immutable El seu nom canvia si canvia el contingut; guardar-lo en memòria cau un any és segur

Abans de publicar, es verifica la construcció tal qual, no el servidor de desenvolupament:

npm run preview
# Serveix dist/ a http://localhost:4173

És el moment de comprovar tres coses que només es veuen aquí: que no hi ha errors a la consola sense els avisos de desenvolupament, que els fragments mandrosos es descarreguen en navegar (mira la pestanya de xarxa) i que el Suspense de cada ruta mostra el seu esquelet i no un salt en blanc.

  1. Revisar la mida abans de publicar

L'informe de dalt ja dona la xifra que importa: 87,65 kB comprimits per al paquet principal. La revisió útil no és «és molt?» en abstracte, sinó comparar amb el pressupost que es va fixar i localitzar qui ocupa què:

npx vite-bundle-visualizer

Un desglossament típic d'aquest projecte:

Dependència Aproximat (gzip) Es pot reduir?
react + react-dom ~45 kB No: és el motor
react-router ~12 kB No, i ja divideix per rutes
@reduxjs/toolkit + react-redux ~15 kB Només eliminant Redux, que aquí es va justificar
@tanstack/react-query ~13 kB No: substitueix molt codi propi
Codi de CicloUrbano ~3 kB Ja està dividit per rutes

La lliçó: en una aplicació ben dividida, el pes és gairebé tot dependències, i la palanca que queda no és minificar millor, sinó triar menys biblioteques. Si el pressupost se supera, la conversa és sobre arquitectura, no sobre configuració.

  1. Variables d'entorn: què és realment públic

Vite exposa al codi només les variables que comencen per VITE_, i les substitueix a la construcció:

# .env.development
VITE_API_URL=http://localhost:3001
VITE_ENTORN=desarrollo

# .env.production
VITE_API_URL=https://api.ciclourbano.example
VITE_ENTORN=produccion
// src/configuracio.js — un únic punt de lectura, tal com es va decidir a 11-01
export const configuracio = {
  apiUrl: import.meta.env.VITE_API_URL,
  entorn: import.meta.env.VITE_ENTORN,
  esProduccio: import.meta.env.PROD,
};

I ara l'advertència més important d'aquesta lliçó, perquè és un error que es comet amb freqüència i té conseqüències reals:

Tot el que posis a import.meta.env acaba escrit, en text llegible, dins dels fitxers JavaScript que es descarrega qualsevol persona que visiti la web. No és una variable «de servidor»: és una constant que s'incrusta al paquet. Qualsevol pot obrir les eines del navegador i llegir-la.

# Comprova-ho tu mateix després de construir:
grep -r "api.ciclourbano.example" dist/assets/
# → apareix, tal qual, al paquet

D'aquí es deriva una frontera clara:

Pot anar a VITE_* No pot anar mai
URL pública de l'API Claus d'API amb permisos d'escriptura
Identificador públic d'un servei d'analítica Secrets de client d'OAuth
Nom de l'entorn, versió, banderes de funcionalitat Credencials de base de dades
Clau publicable d'una passarel·la de pagament Clau secreta de la mateixa passarel·la

El que necessiti un secret necessita un servidor que el guardi i faci la crida per tu. No hi ha drecera. I .env.production no es versiona: al repositori hi va .env.example amb les claus i sense els valors, i els valors reals es configuren a la plataforma de desplegament.

  1. El 404 en recarregar: enrutament en client sobre un servidor estàtic

Aquesta és la fallada que sorprèn tothom en el seu primer desplegament d'una aplicació d'una sola pàgina. En local funciona; desplegat, entrar directament a https://ciclourbano.example/reservas retorna un 404.

La raó és que hi ha dues entitats diferents resolent rutes i només una coneix les teves:

sequenceDiagram
    participant N as Navegador
    participant S as Servidor estàtic
    participant R as React Router
    Note over N,R: Navegació interna: funciona
    N->>R: clic a «Les meves reserves»
    R->>N: pinta PaginaReserves i canvia la URL
    Note over N,R: Recàrrega o enllaç directe: 404
    N->>S: GET /reservas
    S->>S: existeix el fitxer /reservas?
    S--xN: 404 · React Router mai va arribar a executar-se

React Router viu dins del JavaScript que carrega index.html. Si el servidor no lliura index.html, no hi ha React, no hi ha Router i no hi ha ruta. La solució és sempre la mateixa idea —retornar index.html per a qualsevol ruta que no sigui un fitxer real— amb una sintaxi diferent a cada plataforma:

Plataforma Fitxer Contingut
Netlify public/_redirects /* /index.html 200
Vercel vercel.json Vegeu a sota
GitHub Pages public/404.html Una còpia d'index.html (no admet reescriptures)
Nginx Bloc server try_files $uri $uri/ /index.html;
Apache public/.htaccess Vegeu a sota
Cloudflare Pages public/_redirects Igual que Netlify
// vercel.json
{
  "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }],
  "headers": [
    {
      "source": "/assets/(.*)",
      "headers": [{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }]
    }
  ]
}
# public/.htaccess
<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
  # Si el fitxer o el directori existeix, serveix'l tal qual
  RewriteCond %{REQUEST_FILENAME} -f [OR]
  RewriteCond %{REQUEST_FILENAME} -d
  RewriteRule ^ - [L]
  # Qualsevol altra cosa: lliura index.html i deixa que React Router decideixi
  RewriteRule ^ index.html [L]
</IfModule>

Fixa't en el detall del codi 200 a Netlify: és una reescriptura, no una redirecció. La URL que veu l'usuari continua sent /reservas, que és justament el que React Router necessita llegir. Amb un 301 o un 302 la URL canviaria a / i l'usuari acabaria al catàleg.

I la comprovació que mai no s'ha de saltar després de desplegar: obrir una ruta profunda en una pestanya nova i recarregar-la. Navegar-hi des de l'inici no prova res, perquè aquest camí mai no toca el servidor.

  1. Desplegament pas a pas en una plataforma estàtica

El projecte són fitxers estàtics, així que gairebé qualsevol allotjament serveix. Amb una plataforma connectada al repositori, el desplegament queda automatitzat:

Pas 1. Puja el projecte a un repositori remot (GitHub, GitLab). El flux de treball de CI escrit a la lliçó anterior comença a executar-se a cada canvi.

Pas 2. Connecta el repositori a la plataforma i configura la construcció:

Ajust Valor
Comanda de construcció npm run build
Directori de publicació dist
Versió de Node 20 (la mateixa que a CI, per no descobrir diferències tard)
Comanda d'instal·lació npm ci

Pas 3. Afegeix les variables d'entorn al panell de la plataforma: VITE_API_URL amb la URL real de l'API i VITE_ENTORN=produccion. Mai al repositori.

Pas 4. Afegeix el fitxer de reescriptura de la secció anterior. Sense ell, el desplegament «funciona» fins que algú comparteix un enllaç.

Pas 5. Desplega i comprova, en aquest ordre: la portada carrega; una ruta profunda recarregada en pestanya nova funciona; el catàleg porta dades de l'API real; una ruta inexistent mostra la teva pàgina de no trobat; el panell de taller continua vedat a un client; i la consola està neta.

Pas 6. Activa els desplegaments de vista prèvia per branca. Cada branca i cada sol·licitud de canvis obté la seva pròpia URL temporal, així que les revisions deixen de ser «confia en mi» i passen a ser «prova-ho aquí». És, de bon tros, la funció que més canvia la forma de treballar en equip.

  1. L'alternativa del contenidor: Docker i Nginx

Si el desplegament va a la infraestructura pròpia de l'empresa, la forma equivalent és una imatge que construeix i serveix:

# Dockerfile
# --- Etapa 1: construir ---
FROM node:20-alpine AS construccio
WORKDIR /app
# Copiar primer els manifestos aprofita la memòria cau de capes:
# si no canvien, no es reinstal·len les dependències
COPY package*.json ./
RUN npm ci
COPY . .
ARG VITE_API_URL
ENV VITE_API_URL=$VITE_API_URL
RUN npm run build

# --- Etapa 2: servir ---
# La imatge final no porta Node ni node_modules: només fitxers estàtics i Nginx
FROM nginx:alpine AS servei
COPY --from=construccio /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
# nginx.conf
server {
  listen 80;
  root /usr/share/nginx/html;

  gzip on;
  gzip_types text/css application/javascript application/json image/svg+xml;

  # Els fitxers amb hash es poden guardar en memòria cau indefinidament
  location /assets/ {
    expires 1y;
    add_header Cache-Control "public, immutable";
  }

  # index.html mai no es guarda en memòria cau: és qui apunta als assets nous
  location = /index.html {
    add_header Cache-Control "no-cache";
  }

  # La peça clau: qualsevol ruta desconeguda lliura index.html
  location / {
    try_files $uri $uri/ /index.html;
  }
}

Dos detalls que convé entendre. El primer és que la construcció de dues etapes deixa una imatge final d'uns pocs megabytes: Node només existeix mentre es construeix. El segon és l'ARG VITE_API_URL: com que les variables s'incrusten a la construcció, una imatge construïda apunta a una API concreta. No la pots canviar en arrencar el contenidor. Si necessites una imatge que serveixi diversos entorns, la URL s'ha de llegir en temps d'execució (per exemple, d'un /configuracio.json que es demana en arrencar l'aplicació).

  1. L'API en producció: què falta de veritat

Cal dir-ho sense adorns: json-server és una eina de desenvolupament. Accepta qualsevol escriptura de qualsevol, no té autenticació, no valida res i guarda les dades en un fitxer. Desplegar-lo amb dades reals seria un incident de seguretat, no un desplegament.

El que fa falta darrere d'una aplicació com aquesta:

Peça Per què Què implica
API real Persistència fiable, transaccions, integritat Un servei propi (Node, Python, Java…) o una plataforma tipus backend as a service
Autenticació Saber qui demana alguna cosa Testimonis de sessió en galetes httpOnly + Secure + SameSite; renovació i caducitat
Autorització al servidor Que /taller sigui de veritat només de l'operari Comprovació de rol a cada endpoint, no només a la interfície
Validació al servidor validarReserva és una comoditat, no una defensa Repetir les regles al servidor; és l'única còpia que compta
CORS Permetre el teu domini i només aquest Capçaleres amb la llista d'orígens permesos
Límits de taxa Evitar abús i cost descontrolat Tallafocs d'aplicació o límits per IP i per usuari
Bloqueig de sobrereserva Dues persones reservant bici-001 alhora Unicitat i transacció a la base de dades

Val la pena rellegir amb aquesta llum el que es va dir a Rutes Protegides: RutaProtegida i RequereixRol milloren l'experiència i no protegeixen res. Qualsevol pot obrir les eines del navegador, modificar l'estat de la sessió i veure la pantalla del taller. El que impedeix que faci mal no és el teu component: és el servidor rebutjant la petició.

Un últim avís de sensatesa professional: tan bon punt hi hagi dades de persones reals pel mig —noms, correus, ubicacions, pagaments— el disseny d'autenticació, emmagatzematge i permisos l'ha de revisar algú amb experiència en seguretat i protecció de dades abans d'exposar-lo al públic. Tot el domini d'aquest curs és fictici precisament per poder aprendre sense aquest risc.

  1. Després del desplegament: monitorització i mètriques reals

Publicar no és acabar. En producció hi ha navegadors, xarxes i persones que no eren al teu portàtil, i hi ha dues preguntes que només es responen amb dades d'allà.

Falla alguna cosa? El registrarError de src/utilitats/monitoritzacio.js porta tot el curs sent un console.error. Ara és quan connecta amb un servei de veritat:

// src/utilitats/monitoritzacio.js
import { configuracio } from '../configuracio.js';

export function registrarError(error, pilaComponents, context = {}) {
  if (!configuracio.esProduccio) {
    console.error('[CicloUrbano]', error, pilaComponents, context);
    return;
  }

  // En producció s'envia al servei de monitorització.
  // No incloguis mai dades personals a l'informe: identificadors, no noms ni correus.
  enviarInforme({
    missatge: error.message,
    pila: error.stack,
    pilaComponents,
    ruta: window.location.pathname,
    versio: configuracio.versio,
    usuariId: context.usuariId ?? null,
  });
}

Aquest enganxament ja està posat on tenia que estar: el componentDidCatch del LimitError que es va escriure a Límits d'Error. El valor d'haver-ho previst llavors es cobra ara.

Va ràpida per a la gent de veritat? Els mesuraments del Profiler es van fer en un portàtil amb bona xarxa. Les mètriques d'usuaris reals es recullen al mateix navegador i s'envien:

// src/main.jsx
if (configuracio.esProduccio) {
  import('web-vitals').then(({ onLCP, onINP, onCLS }) => {
    onLCP(enviarMetrica);   // Quant triga a pintar-se el principal
    onINP(enviarMetrica);   // Quant triga a respondre a una interacció
    onCLS(enviarMetrica);   // Quant es mou el contingut mentre carrega
  });
}

I fa falta una tercera cosa que no és tècnica: un canal de tornada. Un enllaç visible per informar d'un problema, i algú que el llegeixi. La majoria de les fallades que importen les explica una persona abans que cap mètrica les delati.

  1. Llista de comprovació de llançament

Àrea Comprovació
Construcció npm run build sense avisos · npm run preview recorregut complet · mida dins del pressupost
Enrutament Ruta profunda recarregada en pestanya nova · ruta inexistent mostra el teu 404 · /taller vedat a un client
Rendiment LCP i INP mesurats a la construcció de producció · fragments mandrosos descarregant-se en navegar
Accessibilitat Recorregut complet amb Tab · focus visible · formularis etiquetats · contrast suficient en tots dos temes · auditoria del navegador
SEO bàsic <title> i descripció · idioma a <html lang="ca"> · favicon · robots.txt
Errors registrarError connectat · LimitError amb un missatge humà · cap dada personal als informes
Seguretat Cap secret al paquet (grep sobre dist/) · HTTPS · capçaleres de seguretat · autorització comprovada al servidor
Dades Còpies de seguretat de la base de dades · pla de restauració provat
Operació Analítica respectuosa amb la privacitat · alertes configurades · pla de reversió al desplegament anterior
Persones Canal per informar de fallades · algú de guàrdia el dia del llançament

El punt que més s'oblida i més fa mal és el pla de reversió. Abans de publicar, cal saber com es torna a la versió anterior i quant triga. Les plataformes connectades al repositori ho donen fet amb un botó; amb contenidors, és tornar a desplegar l'etiqueta anterior. Esbrina-ho abans de necessitar-ho.

  1. Següents passos amb aquest projecte

CicloUrbano és viva i té un full de ruta natural, tot ell recolzat en el que ja saps:

Millora Amb què del curs Què guanya
Migrar a TypeScript TypeScript amb React, fitxer a fitxer començant per src/tipus/domini.ts Contractes explícits i refactoritzacions segures; menys proves trivials
Aparador públic SSR i SSG amb Next.js per al catàleg i les fitxes Contingut indexable i primer pintat immediat, al costat de la gestió a l'SPA
Aplicació mòbil React Native amb Expo, reutilitzant validacions, hooks i slices Presència nativa amb la major part de la lògica compartida
Internacionalització Extreure els textos i afegir català i anglès Abast real de l'aplicació
Mode sense connexió Un service worker i la persistència de la memòria cau de Query Una app usable al carrer amb mala cobertura
Pagaments i facturació Una passarel·la real, amb la clau secreta al servidor El pas de prototip a producte
Panell de mètriques El component de gràfiques ja aïllat en un fragment mandrós Decisions de negoci amb dades

El millor consell per triar és el mateix que va ordenar el projecte des del principi: increments verticals. Una millora completa, de la interfície a la prova, abans de començar la següent.

  1. Com continuar aprenent

  • La documentació oficial de React és, avui, excel·lent. Les seves pàgines sobre «no necessites un efecte» i sobre com estructurar l'estat expliquen millor que cap tutorial per què les coses són com són. Torna-hi amb l'experiència que ja tens: es llegeixen diferents.
  • Llegeix el codi de les biblioteques que fas servir. React Router, Redux Toolkit i TanStack Query són llegibles i estan ben escrites. Entendre com resolen un problema ensenya més que qualsevol curs, inclòs aquest.
  • Escriu la teva pròpia versió reduïda d'alguna cosa que facis servir: un useQuery mínim amb memòria cau, un enrutador de cinquanta línies. Res consolida un model mental com reimplementar-lo.
  • Reprodueix les fallades que trobis en un exemple mínim abans de demanar ajuda. La meitat de les vegades es resolen en l'intent, i l'altra meitat reps una resposta molt millor.
  • Contribueix. Comença per documentació i per reproduir incidències; és la porta d'entrada més honesta a un projecte de codi obert.
  • Mantén l'escepticisme amb el que és nou. L'ecosistema de React es mou ràpid i no tot el que apareix sobreviu. La manera de distingir la moda de la millora és preguntar-se quin problema concret resol i si tu el tens.

Errors Comuns i Consells

  • Desplegar sense la regla de reescriptura. L'aplicació sembla funcionar fins que algú recarrega o comparteix un enllaç. Comprova sempre una ruta profunda en pestanya nova.
  • Posar un secret en una variable VITE_. Queda en text llegible al paquet. Si alguna cosa ha de ser secreta, necessita un servidor.
  • Guardar en memòria cau index.html durant molt de temps. Els usuaris es queden clavats a la versió anterior encara que els assets nous ja estiguin publicats. index.html sense memòria cau, assets amb hash i memòria cau llarga.
  • Confiar en RutaProtegida com a seguretat. És experiència d'usuari. L'autorització es comprova al servidor, a cada endpoint, sempre.
  • Provar el rendiment al servidor de desenvolupament. Els números no s'assemblen als de producció. Mesura sobre npm run preview o sobre el desplegament real.
  • Desplegar un divendres a la tarda sense pla de reversió. No és una broma: si no saps com tornar enrere i quant triga, no estàs a punt per publicar.
  • Consell final: automatitza el desplegament el mateix dia que en facis el primer a mà. Un desplegament que requereix recordar passos és un desplegament que algú farà malament.

Exercicis

Exercici 1. Desplega CicloUrbano en una plataforma estàtica amb la reescriptura correcta i les capçaleres de memòria cau de la secció 1. Documenta al README.md el procediment i el pla de reversió, i verifica amb grep sobre dist/ que no s'ha colat cap secret.

Exercici 2. Prepara el projecte per servir-se des d'una subruta (https://ejemplo.example/ciclourbano/) en lloc de l'arrel del domini. Esbrina què cal canviar a Vite, a React Router i a la configuració del servidor.

Exercici 3. Afegeix al flux d'integració contínua un pas que faci fallar la construcció si el paquet principal supera un pressupost de 100 kB comprimits, perquè la mida no es degradi sense que ningú se n'adoni.

Solucions

Solució 1.

proyecto/
├── public/_redirects        →  /*    /index.html   200
└── vercel.json (si és Vercel, amb rewrites i headers)
# Verificació prèvia obligatòria: cap secret al paquet
npm run build
grep -rEi "secret|password|sk_live|private_key" dist/assets/ && echo "REVISAR" || echo "net"

# Comprovació posterior al desplegament
curl -I https://ciclourbano.example/reservas          # ha de retornar 200 i text/html
curl -I https://ciclourbano.example/assets/index-*.js # ha de portar Cache-Control: immutable

Al README.md:

## Desplegament

Automàtic en integrar a `main`. La plataforma executa `npm ci` i `npm run build`
i publica `dist/`. Variables necessàries: `VITE_API_URL`, `VITE_ENTORN`.

### Comprovació després de desplegar
1. Obrir `/reservas` en una pestanya nova i recarregar → ha de carregar (no 404).
2. Ruta inexistent → pàgina pròpia de no trobat.
3. `/taller` amb sessió de client → «sense permisos».
4. Consola del navegador sense errors.

### Reversió
Panell de desplegaments → seleccionar l'anterior correcte → «Restaurar».
Temps estimat: menys de 2 minuts. No requereix reconstruir.

Solució 2. Cal tocar tres llocs, i oblidar-se de qualsevol d'ells produeix una pantalla en blanc o enllaços trencats:

// 1. vite.config.js — prefix de les rutes dels assets a index.html
export default defineConfig({
  base: '/ciclourbano/',
  plugins: [react()],
});
// 2. src/rutes.jsx — React Router ha d'ignorar el prefix en comparar rutes
export const router = createBrowserRouter(rutes, {
  basename: '/ciclourbano',
});
# 3. Servidor — la reescriptura ha d'apuntar a l'index.html de la subruta
location /ciclourbano/ {
  alias /usr/share/nginx/html/;
  try_files $uri $uri/ /ciclourbano/index.html;
}

Comprovació: dist/index.html ha de referenciar /ciclourbano/assets/..., i una recàrrega a /ciclourbano/reservas ha de funcionar. Els enllaços interns amb <Link to="/reservas"> es continuen escrivint sense el prefix: l'afegeix el basename.

Solució 3.

// package.json
{
  "scripts": {
    "presupuesto": "node scripts/comprobar-presupuesto.js"
  }
}
// scripts/comprobar-presupuesto.js
import { readdirSync, readFileSync } from 'node:fs';
import { gzipSync } from 'node:zlib';
import { join } from 'node:path';

const LIMIT_KB = 100;
const CARPETA = 'dist/assets';

// El paquet principal és el que comença per index- i acaba en .js
const principal = readdirSync(CARPETA).find((f) => /^index-.*\.js$/.test(f));

if (!principal) {
  console.error('No s\'ha trobat el paquet principal. S\'ha executat npm run build?');
  process.exit(1);
}

const bytes = gzipSync(readFileSync(join(CARPETA, principal))).length;
const kb = bytes / 1024;

console.log(`${principal}: ${kb.toFixed(2)} kB comprimits (límit ${LIMIT_KB} kB)`);

if (kb > LIMIT_KB) {
  console.error(
    `\nPressupost superat en ${(kb - LIMIT_KB).toFixed(2)} kB.\n` +
    'Revisa les dependències noves amb: npx vite-bundle-visualizer'
  );
  process.exit(1);   // El codi de sortida diferent de zero és el que trenca la construcció a CI
}
# .github/workflows/pruebas.yml — afegit al treball d'extrem a extrem
      - run: npm run build
      - run: npm run presupuesto     # Fa fallar el treball si el paquet ha engreixat

La clau és el process.exit(1): sense ell, l'script informa però la integració contínua continua en verd i el pressupost es converteix en un suggeriment que ningú atén.

Conclusió

Vas començar aquest curs amb un <div id="root"> buit i la pregunta de per què algú necessitaria una biblioteca per pintar una llista de bicicletes. Acabes amb una aplicació completa, provada en quatre nivells, mesurada, dividida en fragments, accessible i desplegada, i amb criteri propi per decidir com construir la següent.

El camí ha estat aquest. Als fonaments vas entendre que React és declaratiu i que la interfície és una funció de l'estat, i vas veure què passa de veritat entre un canvi d'estat i un píxel a la pantalla. Amb els components, les props i l'estat vas aprendre a delimitar peces i a parametritzar-les, i amb els esdeveniments, els formularis i l'accessibilitat vas fer que responguessin a persones, no només a dades. Als conceptes avançats va aparèixer la disciplina: elevar l'estat a l'avantpassat comú, compondre en lloc d'heretar, i capturar les fallades abans que es mengin la pantalla. Els hooks et van donar el vocabulari complet —estat, sincronització amb l'exterior, referències, context, reductors— i, sobretot, la capacitat d'extreure la teva pròpia lògica reutilitzable. React Router va convertir una pantalla en una aplicació amb URLs compartibles i accessos controlats. La gestió de l'estat va posar ordre en el caos amb una idea que val més que qualsevol biblioteca: cada dada té un lloc on ha de viure, i les dades del servidor no són teves. El rendiment et va ensenyar a mesurar abans de tocar i a no confondre optimització amb decoració. Les proves et van donar la llibertat de refactoritzar sense por, provant comportament i no implementació. Els temes avançats van ampliar el mapa fins al servidor, els tipus i el mòbil. I el projecte va demostrar que tot això encaixa.

El que t'emportes no és una llista d'APIs, que canviaran. És la manera de pensar que hi ha a sota: descriure la interfície en funció de l'estat, posar cada dada al seu lloc, mesurar abans d'optimitzar, provar el comportament visible, i no confiar mai en el client per al que ha de garantir el servidor. Això continua sent cert quan canvia la versió, la biblioteca de moda o el framework sencer.

Ja tens el que fa falta. Construeix alguna cosa.

Curs de React

Mòdul 1: Introducció a React

Mòdul 2: Components de React

Mòdul 3: Treballar amb Esdeveniments

Mòdul 4: Conceptes Avançats de Components

Mòdul 5: Hooks de React

Mòdul 6: Enrutament a React

Mòdul 7: Gestió de l'Estat

Mòdul 8: Optimització del Rendiment

Mòdul 9: Proves a React

Mòdul 10: Temes Avançats

Mòdul 11: Projecte: Construir una Aplicació Completa

© Copyright 2026. Tots els drets reservats