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
- La construcció per a producció
- Revisar la mida abans de publicar
- Variables d'entorn: què és realment públic
- El 404 en recarregar: enrutament en client sobre un servidor estàtic
- Desplegament pas a pas en una plataforma estàtica
- L'alternativa del contenidor: Docker i Nginx
- L'API en producció: què falta de veritat
- Després del desplegament: monitorització i mètriques reals
- Llista de comprovació de llançament
- Següents passos amb aquest projecte
- Com continuar aprenent
- 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:
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:
É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.
- 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è:
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ó.
- 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.envacaba 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 paquetD'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.
- 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.
- 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.
- 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ó).
- 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.
- 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.
- 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.
- 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.
- 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
useQuerymí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.htmldurant molt de temps. Els usuaris es queden clavats a la versió anterior encara que elsassetsnous ja estiguin publicats.index.htmlsense memòria cau,assetsamb hash i memòria cau llarga. - Confiar en
RutaProtegidacom 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 previewo 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: immutableAl 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.
// 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 engreixatLa 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
- Què és React?
- Configuració de l'Entorn de Desenvolupament
- Hola Món amb React
- JSX: Extensió de Sintaxi de JavaScript
- Com Renderitza React: Virtual DOM i Reconciliació
Mòdul 2: Components de React
- Entendre els Components
- Components Funcionals vs de Classe
- Props: Passar Dades als Components
- State: Gestió de l'Estat del Component
- Estils en els Components: CSS, Mòduls i Utilitats
Mòdul 3: Treballar amb Esdeveniments
- Gestió d'Esdeveniments a React
- Renderitzat Condicional
- Llistes i Claus
- Formularis i Components Controlats
- Validació de Formularis i Components No Controlats
- Accessibilitat en Components Interactius
Mòdul 4: Conceptes Avançats de Components
- Elevar l'Estat
- Composició vs Herència
- Mètodes del Cicle de Vida de React
- Hooks: Introducció i Ús Bàsic
- Límits d'Error: Capturar Fallades a la Interfície
Mòdul 5: Hooks de React
- Hook useState
- Hook useEffect
- Hook useRef i Accés al DOM
- Hook useContext
- Hook useReducer
- Hooks Personalitzats
Mòdul 6: Enrutament a React
- Introducció a React Router
- Configuració de React Router
- Rutes Imbricades
- Navegació Programàtica
- Rutes Protegides i Control d'Accés
Mòdul 7: Gestió de l'Estat
- Introducció a la Gestió de l'Estat
- API de Context
- Redux: Introducció i Configuració
- Redux: Accions i Reductors
- Redux: Connectar-lo a React
- Estat del Servidor: Peticions, Memòria Cau i Sincronització
Mòdul 8: Optimització del Rendiment
- Tècniques d'Optimització del Rendiment a React
- Memoïtzació amb React.memo
- Hooks useMemo i useCallback
- Divisió de Codi i Càrrega Mandrosa
- Mesurar el Rendiment amb React DevTools Profiler
Mòdul 9: Proves a React
- Introducció a les Proves
- Proves Unitàries amb Jest
- Proves de Components amb React Testing Library
- Proves de Codi Asíncron i Simulació d'APIs
- Proves d'Extrem a Extrem amb Cypress
Mòdul 10: Temes Avançats
- Renderitzat al Servidor (SSR) amb Next.js
- Generació de Llocs Estàtics (SSG) amb Next.js
- Suspense i React Server Components
- TypeScript amb React
- React Native: Creació d'Aplicacions Mòbils
