@withpanache/nextjs
v0.13.0
Published
Next.js plugin for Panache monitoring. Collects dependency manifests at build time and applies security defaults.
Maintainers
Readme
@withpanache/nextjs
Next.js plugin for Panache. Monitors your app's security, performance, and dependencies with zero config.
What it does
- Security headers : applies production-ready defaults (CSP, HSTS, X-Frame-Options, Permissions-Policy, etc.) with full TypeScript autocomplete for customization
- Dependency monitoring : collects your dependency tree at build time and pushes it to Panache for vulnerability tracking
- Zero impact : headers are injected at config level (no middleware), manifest push runs in a detached subprocess (never blocks your build)
Install
npm install @withpanache/nextjs
# or
pnpm add @withpanache/nextjs
# or
yarn add @withpanache/nextjsRequires Next.js 14 or later.
Quick start
// next.config.ts
import { withPanache } from "@withpanache/nextjs"
export default withPanache({
// your existing Next.js config
})That's it. Your app now has security headers and X-Powered-By is disabled.
To enable dependency monitoring, add your site token:
export default withPanache(
{ /* next config */ },
{ token: process.env.PANACHE_SITE_TOKEN }
)Or set the PANACHE_SITE_TOKEN environment variable (the plugin picks it up automatically).
Options
interface PanacheOptions {
/** Site token from the Panache dashboard. Also reads PANACHE_SITE_TOKEN env var. */
token?: string
/** Ingest API URL. Default: "https://withpanache.dev/api/v1/ingest" */
apiUrl?: string
/** Security headers. true = all defaults, false = skip, object = customize. Default: true */
security?: boolean | SecurityHeadersConfig
/** Project default branch (mirrors sites.default_branch in the dashboard). Default: "main" */
defaultBranch?: string
/** CSP violation monitoring. Disabled unless a token is provided. */
cspMonitoring?: CspMonitoringConfig
}Security headers
By default, withPanache injects the following headers on all routes:
| Header | Default value |
|---|---|
| Strict-Transport-Security | max-age=63072000; includeSubDomains |
| Content-Security-Policy | default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https:; frame-ancestors 'self' |
| X-Content-Type-Options | nosniff |
| X-Frame-Options | SAMEORIGIN |
| Referrer-Policy | strict-origin-when-cross-origin |
| Permissions-Policy | camera=(), microphone=(), geolocation=() |
These defaults are permissive enough for most Next.js apps while passing Panache's security checks.
Customizing headers
Pass a SecurityHeadersConfig object to override or disable individual headers:
export default withPanache(nextConfig, {
security: {
// Disable HSTS (e.g. behind a reverse proxy that handles it)
strictTransportSecurity: false,
// Custom referrer policy
referrerPolicy: "no-referrer",
// Stricter X-Frame-Options
xFrameOptions: "DENY",
},
})Set security: false to disable all security headers (Panache will still monitor your app if a token is provided).
CSP with typed directives
CSP can be configured as a raw string or as a typed object with autocomplete:
export default withPanache(nextConfig, {
security: {
contentSecurityPolicy: {
// Your sources are added to the defaults, no need to repeat 'self'
"script-src": ["https://cdn.example.com"],
"connect-src": ["https://api.example.com", "https://analytics.example.com"],
"img-src": ["https://images.example.com"],
},
},
})When using the object form, your sources are added to the defaults (deduplicated). You don't need to repeat 'self' or other default values. Directives you don't mention keep their default values entirely.
To replace a specific directive instead of extending it, use the replace() helper:
import { withPanache, replace } from "@withpanache/nextjs"
export default withPanache(nextConfig, {
security: {
contentSecurityPolicy: {
"script-src": ["https://cdn.example.com"], // extends defaults
"connect-src": replace(["'self'", "https://api.example.com"]), // replaces defaults
},
},
})To fully replace the entire CSP, pass a raw string:
export default withPanache(nextConfig, {
security: {
contentSecurityPolicy: "default-src 'none'; script-src 'self'",
},
})Permissions-Policy with typed features
Same pattern as CSP. Object form adds to defaults, string form replaces:
export default withPanache(nextConfig, {
security: {
permissionsPolicy: {
// Merge with defaults (camera, microphone, geolocation remain disabled)
fullscreen: ["self"],
payment: [],
},
},
})How merging works
- If you already define a
headers()function in your Next.js config, Panache appends its headers to your catch-all route (/(.*)) without overriding any header you've set. - If you don't have a
headers()function, Panache creates one. - Your headers always take priority. Panache never overwrites an existing header.
Dev mode
In development (NODE_ENV !== "production"), the default CSP includes 'unsafe-eval' in script-src because React's development build requires it. This is automatically removed in production builds.
CSP monitoring
When a CSP token is provided, the plugin attaches a Reporting-Endpoints
header pointing at Panache's report ingest service. Browsers POST
Content-Security-Policy violations directly to Panache, which aggregates
them hourly into per-source observations in the dashboard. Useful for
catching third-party drift, misconfigured CSP rules, and rogue scripts.
Enable
Generate a CSP token in the Panache dashboard (Site settings → CSP monitoring), then set the env var :
PANACHE_CSP_TOKEN=pncsp_<32 hex chars>The plugin picks it up automatically. No code change needed beyond having
withPanache(...) already wrapping your config.
Alternatively, pass it explicitly :
export default withPanache(nextConfig, {
cspMonitoring: { token: process.env.PANACHE_CSP_TOKEN },
})Learning Mode
Existing sites usually need a discovery period before enforcing CSP.
Learning Mode emits Content-Security-Policy-Report-Only instead of
Content-Security-Policy, so violations are reported but not blocked :
PANACHE_CSP_LEARNING_MODE=1Or via plugin options :
export default withPanache(nextConfig, {
cspMonitoring: {
token: process.env.PANACHE_CSP_TOKEN,
learningMode: true,
},
})Truthy values for the env var : 1, true, yes (case-insensitive).
Anything else, including absence, defaults to enforce mode.
Once you've used the dashboard observations to pin down the legitimate third parties for your site, drop Learning Mode and the same CSP becomes enforced.
Branch-aware reports
Reports include the branch detected from your CI environment
(GITHUB_REF_NAME, CI_COMMIT_BRANCH, VERCEL_GIT_COMMIT_REF, etc., or
local git branch --show-current). Non-default branches send
?b=<branch> on the report URL so observations are partitioned per
branch in the dashboard. Set defaultBranch if your default isn't main :
export default withPanache(nextConfig, {
defaultBranch: "develop",
cspMonitoring: { token: process.env.PANACHE_CSP_TOKEN },
})Self-hosted endpoint
For self-hosted Panache deployments, override the report endpoint base URL :
PANACHE_CSP_ENDPOINT=https://csp.your-panache-domain.comOr :
export default withPanache(nextConfig, {
cspMonitoring: {
token: process.env.PANACHE_CSP_TOKEN,
endpoint: "https://csp.your-panache-domain.com",
},
})How it interacts with security.contentSecurityPolicy
CSP monitoring composes with the existing CSP header logic :
- The
report-to panachedirective is appended to whatever CSP you have (default, custom object, or raw string). - A matching
Reporting-Endpoints: panache="<endpoint>/<token>"header is set automatically. - If you set
security: false, CSP monitoring is also disabled (the plugin won't attach any headers).
Dependency monitoring
During next build, the plugin:
- Detects your package manager (pnpm, npm, yarn classic, yarn berry)
- Collects the full dependency tree with versions
- Pushes it to the Panache API in a detached subprocess
This runs in the background and never blocks or slows down your build. If the push fails (no network, invalid token), it fails silently.
Supported package managers
- pnpm
- npm
- yarn (classic and berry)
- bun (detection only, not yet supported)
CI/CD detection
The plugin automatically detects git SHA, branch, and preview URL from common CI environments:
- Git SHA :
GITHUB_SHA,CI_COMMIT_SHA,VERCEL_GIT_COMMIT_SHA,CF_PAGES_COMMIT_SHA,COMMIT_REF, or localgit rev-parse HEAD - Branch :
GITHUB_REF_NAME,CI_COMMIT_BRANCH,VERCEL_GIT_COMMIT_REF,CF_PAGES_BRANCH,BRANCH, or localgit branch --show-current - Preview URL :
VERCEL_URL,CF_PAGES_URL,DEPLOY_URL
TypeScript
All types are exported for use in your config:
import type {
PanacheOptions,
SecurityHeadersConfig,
CspDirectives,
CspDirectiveName,
CspSourceValue,
CspMonitoringConfig,
PermissionsPolicyDirectives,
PermissionsPolicyFeature,
ReferrerPolicy,
} from "@withpanache/nextjs"License
MIT
