Ofusca un build de Next.js

@afterpack/next ofusca el JavaScript de cliente dentro de next build, con Turbopack o webpack, en SSR o en exportación estática.

@afterpack/next ofusca tu JavaScript de cliente desde dentro de next build. withAfterpack devuelve una configuración que lleva compiler.runAfterProductionCompile, el hook de build propio de Next, que Next llama una sola vez con cualquiera de los dos bundlers.

Envolver tu configuración es toda la integración. No hay script de ciclo de vida ni comando aparte.

Instalación

$ npm install -D @afterpack/next
// next.config.ts
import type { NextConfig } from "next";
import { withAfterpack } from "@afterpack/next";

const nextConfig: NextConfig = {
  // your existing config
};

export default withAfterpack(nextConfig);

La forma de función de una configuración de Next funciona igual. withAfterpack(async (phase, ctx) => ({ … })) devuelve una función envuelta.

Build

next build emite ahora salida ofuscada, sea cual sea el bundler que produjo .next/ (Turbopack o webpack). next dev no se toca. Cada invocación de next build ejecuta el hook, incluido un job de CI que lo llama directamente, y no hay ningún script de ciclo de vida que una instalación pueda omitir. Si el hook lanza una excepción, el build falla con código de salida 1.

El hook ofusca solo el JavaScript que se sirve al cliente: .next/static/chunks, más out/ en una exportación estática. .next/server nunca llega a un navegador y no se ofusca. El hook se ejecuta después de la compilación y antes del prerenderizado y de la exportación estática, así que una exportación estática copia chunks ofuscados en out/.

Next escribe los chunks antes de que se ejecute el hook, así que tus chunks sin ofuscar quedan en disco durante aproximadamente un segundo del build, y cualquier cosa que pueda leer .next/ en ese intervalo puede leerlos. Los plugins integrados en el pipeline (Vite, Rollup, webpack, Parcel) devuelven en cambio el bundle ofuscado al bundler, de modo que nunca se escribe nada sin ofuscar.

Un hook compiler.runAfterProductionCompile que ya tengas configurado se compone en lugar de reemplazarse. El tuyo se ejecuta primero y el de AfterPack después, así que el tuyo ve los mismos bytes que vería sin AfterPack instalado.

El hook captura las opciones que pasas a withAfterpack. Tienen prioridad sobre las variables de entorno AFTERPACK_*, que a su vez tienen prioridad sobre afterpack.json. Usa paths.exclude para dejar un archivo sin tocar.

El protectionMap.html combinado se escribe en .afterpack/, ignorado por git, en la raíz de tu proyecto.

Verificación

Cada build protegido escribe un recibo en .next/.afterpack-protection.json: la versión del motor, la semilla, el bundler, el id del build y un sha256 por cada archivo ofuscado. Compruébalo en tu paso de despliegue:

$ npx afterpack@latest verify .

Sale con un código distinto de cero cuando falta el recibo, cuando pertenece a otro build o cuando algún archivo registrado ya no tiene el hash con el que se ofuscó.

El Protection Map se escribe por defecto solo cuando el bundler emitió un source map y el build no se ejecuta en CI (CI=true o CI=1). Next.js no emite source maps para el navegador a menos que productionBrowserSourceMaps esté activado, así que el mapa está desactivado por defecto. Actívalo de forma explícita para ver los artefactos completos en local:

export default withAfterpack(nextConfig, { protectionMap: { enabled: true } });

El informe se guarda en .afterpack/protectionMap.html, ignorado por git. Contiene todo tu código fuente. Nunca lo subas al repositorio ni lo sirvas.

Nunca publiques source maps en producción

Un archivo .map contiene tu código fuente original. Establece productionBrowserSourceMaps: true solo cuando quieras que el Protection Map muestre el código fuente original. El hook elimina los maps del árbol servido y mantiene desactivado el comentario //# sourceMappingURL en producción.

Opciones

Cualquier clave de configuración puede pasarse como segundo argumento de withAfterpack, por ejemplo withAfterpack(nextConfig, { preset: "hard", complexity: 40 }).

Directivas

El plugin lee las directivas de tu código fuente, y necesitan productionBrowserSourceMaps: true para localizarse en los chunks compilados. Consulta Directivas.

Casos límite

  • experimental.sri se rechaza. Next calcula el hash de integridad de cada recurso mientras lo escribe, antes de que pueda ejecutarse ningún hook de build, así que el navegador bloquea un chunk reescrito después. Elimina experimental.sri o elimina withAfterpack.
  • Apunta a .next/, no a dist/. Si usas la CLI genérica en lugar de este paquete, npx afterpack@latest .next/static/chunks es el destino equivalente, pero no elimina por ti los maps servidos.
  • Falla de forma segura (fail closed) ante archivos sin ofuscar, sin posibilidad de anularlo. Si el motor no puede ofuscar un archivo de forma segura, el build falla e indica de qué archivo se trata. Excluye el archivo del proceso por completo con paths.exclude si nunca debe ofuscarse.

Siguiente