# Obfuscate a Next.js build

@afterpack/next obfuscates the client JavaScript inside next build, on Turbopack or webpack, SSR or static export.

Source: https://www.afterpack.dev/docs/frameworks/nextjs

`@afterpack/next` obfuscates your client JavaScript from inside `next build`. `withAfterpack` returns a config carrying `compiler.runAfterProductionCompile`, Next's own build hook, which Next calls once for either bundler.

Wrapping your config is the whole integration. There is no lifecycle script and no separate command.

## Install

```bash
npm install -D @afterpack/next
```

```ts
// next.config.ts
import type { NextConfig } from "next";
import { withAfterpack } from "@afterpack/next";

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

export default withAfterpack(nextConfig);
```

The function form of a Next config works the same way. `withAfterpack(async (phase, ctx) => ({ … }))` returns a wrapped function.

## Build

`next build` now emits obfuscated output, whichever bundler produced `.next/` (Turbopack or webpack). `next dev` is untouched. Every invocation of `next build` runs the hook, including a CI job that calls it directly, and there is no lifecycle script for an install to skip. If the hook throws, the build fails with exit code 1.

The hook obfuscates the client-served JavaScript only: `.next/static/chunks`, plus `out/` for a static export. `.next/server` never reaches a browser and is not obfuscated. The hook runs after compilation and before prerendering and static export, so a static export copies obfuscated chunks into `out/`.

Next writes the chunks before the hook runs, so your unobfuscated chunks sit on disk for about a second during the build, and anything that can read `.next/` in that window can read them. The in-pipeline plugins ([Vite](https://www.afterpack.dev/docs/frameworks/vite), [Rollup](https://www.afterpack.dev/docs/frameworks/rollup), [webpack](https://www.afterpack.dev/docs/frameworks/webpack), [Parcel](https://www.afterpack.dev/docs/frameworks/parcel)) hand the obfuscated bundle back to the bundler instead, so nothing unobfuscated is ever written.

A `compiler.runAfterProductionCompile` hook you already set is composed rather than replaced. Yours runs first, AfterPack's second, so yours sees the bytes it would see without AfterPack installed.

Options passed to `withAfterpack` are closed over by the hook. They outrank the `AFTERPACK_*` environment variables, which outrank `afterpack.json`. Use [`paths.exclude`](https://www.afterpack.dev/docs/config#paths-exclude) to leave a file untouched.

The combined [`protectionMap.html`](https://www.afterpack.dev/docs/protection-map) is written to the gitignored `.afterpack/` at your project root.

## Verify

Every protected build writes a receipt to `.next/.afterpack-protection.json`: the engine version, the seed, the bundler, the build id, and a sha256 per obfuscated file. Assert it in your deploy step:

```bash
npx afterpack@latest verify .
```

It exits non-zero when the receipt is missing, when it belongs to a different build, or when any recorded file no longer hashes to the value it was obfuscated to.

Because `next build` sets `NODE_ENV=production`, the production-safe posture applies out of the box, and the [Protection Map](https://www.afterpack.dev/docs/protection-map) is off by default. Opt back in explicitly to see the full artifacts locally:

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

The report lands in the gitignored `.afterpack/protectionMap.html`. It contains your full source. Never commit it or serve it.

> **Never ship source maps to production**
>
> A `.map` file contains your original source. Set `productionBrowserSourceMaps: true` only when you want the Protection Map to show original source. The hook strips the maps from the served tree and keeps the `//# sourceMappingURL` comment off in production.

## Options

Every [configuration key](https://www.afterpack.dev/docs/config) can be passed as the second argument to `withAfterpack`, for example `withAfterpack(nextConfig, { preset: "hard", complexity: 40 })`.

## Directives

Directives are read from your source by the plugin, and need `productionBrowserSourceMaps: true` to be located in the built chunks. See [Directives](https://www.afterpack.dev/docs/directives).

## Edge cases

- **`experimental.sri` is refused.** Next computes each asset's integrity hash while it writes the asset, before any build hook can run, so a chunk rewritten afterwards is blocked by the browser. Remove `experimental.sri`, or remove `withAfterpack`.
- **Point at `.next/`, not `dist/`.** If you use [the generic CLI](https://www.afterpack.dev/docs/cli#usage) instead of this package, `npx afterpack@latest .next/static/chunks` is the equivalent target, but it does not strip served maps for you.
- **[Fail-closed](https://www.afterpack.dev/docs/diagnostics#fail-closed-no-exceptions) on unobfuscated files, with no override.** If the engine cannot safely obfuscate a file, the build fails and names the offender. Exclude the file from the pass entirely with [`paths.exclude`](https://www.afterpack.dev/docs/config#paths-exclude) if it should never be obfuscated.

## Next

- [Frameworks](https://www.afterpack.dev/docs/frameworks): the full matrix.
- [Quickstart](https://www.afterpack.dev/docs/quickstart): install, build, and verify.
- [Builds & CI](https://www.afterpack.dev/docs/builds): seeds, dev-vs-prod, promoting the same bytes.
- [Protection Map](https://www.afterpack.dev/docs/protection-map): reading the report.
- [Directives](https://www.afterpack.dev/docs/directives): every marker key, and what it does.
- [Configuration](https://www.afterpack.dev/docs/config): the engine config surface.
