← Back to Blog

Static-Site Generator Deployment Hardening. Eleventy, Hugo, Astro, Gatsby, Next, Jekyll, SvelteKit, Nuxt

Static-Site Generator Deployment Hardening. Eleventy, Hugo, Astro, Gatsby, Next, Jekyll, SvelteKit, Nuxt

Static-site generators sell simplicity: build to a folder, deploy the folder, done. The reality of running SSGs in production is slightly messier. Each generator has its own set of deployment gotchas that show up only after you launch, usually as "wait, why is my draft post indexed?" or "why are my source maps public?" This post covers the ten most common, organized by generator. The Platform Audit now detects all eight major SSGs and flags these gaps where it can.

Eleventy (11ty)

Wrong publish directory. Eleventy builds to _site/ by default. Your Netlify / Vercel / Cloudflare Pages publish directory must be _site, not . (the project root). If you point at ., the deploy ships your Markdown source files as-is alongside the built HTML. Visitors can read /src/blog/draft.md directly.

Fix: netlify.toml → publish = "_site". Vercel dashboard → Build & Output → Output Directory = _site. Cloudflare Pages → Build output directory = _site.

Plaintext frontmatter in deployed output. If your .eleventy.js doesn't strip frontmatter consistently, the top of your HTML may contain Markdown-style ---\ntitle: Foo\n--- leftover. Search your deployed output for leading ---.

Hugo

Draft pages published. hugo by default excludes draft posts; hugo --buildDrafts includes them. If your CI script accidentally runs the latter, every draft goes live.

Fix: your CI build command should be hugo --minify (or just hugo). Never hugo -D or hugo --buildDrafts in production.

Generator meta version exposed. Hugo's default <meta name="generator" content="Hugo X.Y.Z"> tag tells attackers your Hugo version so they can match against any known CVEs. Add to config.toml:

disableHugoGeneratorInject = true

Astro

Dev-only routes shipped. Astro's src/pages/_hidden.astro and similar underscore-prefixed files shouldn't render. But if a typo drops the underscore, they ship. Check dist/ before deploying.

View Transitions without fallback. <ViewTransitions /> works in Chromium. Safari got it later. Firefox is flagged but not default. If you rely on VT for navigation, a Firefox visitor without the flag gets a blank page. Test in all three engines.

Gatsby

Page-data JSON over-fetching. Gatsby prefetches /page-data/<path>/page-data.json for every visible link. On content-heavy pages this fires dozens of requests. Your CDN bill grows. Use <Link> with preload={false} where it matters.

Stale build cache. gatsby develop caches aggressively in .cache/. When you change a plugin config and nothing updates, the build has cached the old state. gatsby clean && gatsby build fixes it.

Next.js

Source maps shipped to production. If you don't explicitly set productionBrowserSourceMaps: false in next.config.js, Next ships .js.map files to the public _next/static/ path. Attackers get readable JavaScript and your original module names.

Fix:

// next.config.js
module.exports = {
  productionBrowserSourceMaps: false
};

Missing output: 'export' for static deploys. Next.js supports static export (SSG), server-rendered (SSR), and incremental static regeneration (ISR). If you want pure static output, set output: 'export' — otherwise you'll accidentally deploy a Node server you don't want.

Jekyll

GitHub Pages pins Jekyll version. GH Pages uses its own Jekyll version, which may lag upstream. Plugins in _config.yml may require a version GH Pages doesn't run. If your site builds locally and fails on GH Pages, the version mismatch is usually why. Use GitHub Actions to run your own Jekyll build and publish the result.

Default theme exposes generator version. <meta name="generator" content="Jekyll v4.3.2"> ships by default on most themes. Remove or override the header tag in your layout.

SvelteKit

Wrong adapter for your target. SvelteKit has five+ adapters: adapter-static, adapter-node, adapter-netlify, adapter-vercel, adapter-cloudflare. Using adapter-node and deploying to Netlify Pages (not Netlify Functions) ships a Node server where only static files can be served. The site loads the index.html but every route returns 404.

Fix: pick the adapter for your target.

.svelte-kit build folder leaked. If your publish directory is wrong, the .svelte-kit/ internal build folder can end up alongside your public output. That folder contains your component source.

Nuxt

Nuxt 2 is end-of-life. As of June 2024 Nuxt 2 is EOL. No security patches. Sites still running Nuxt 2 are accumulating vulnerability debt. Migrate to Nuxt 3.

SSR on a static host. Nuxt 3 defaults to SSR. If you're deploying to a static-only host without adapter-static, every route returns 404 at the edge even though the build succeeded.

All SSGs: universal deployment hardening

Regardless of generator:

  • Never deploy the .git directory. Use a build command that copies only the output folder. See the universal file-exposure post for why.
  • Never deploy .env. Secrets belong in your platform's environment-variable dashboard, not in the build output.
  • Strip source maps in production unless you actively use them for error reporting (Sentry, Datadog, Rollbar).
  • Set a Strict-Transport-Security header via your hosting config file (_headers on Netlify, vercel.json on Vercel).
  • Block .DS_Store and Thumbs.db in your deployment ignore list so macOS / Windows metadata never ships.
  • Test the deployed output with the Platform Audit, Mega Security Analyzer, and /tools/uptime-check/ before announcing the launch.

The detection

The Platform Audit now detects all eight SSGs from their public HTML signatures. Each detection triggers a generator-specific check set (generator version, build output hints, common deployment-pattern issues). For universal file exposure across any generator, use the Mega Security Analyzer.

Related reading

Fact-check notes and sources

  • Eleventy, Hugo, Astro, Gatsby, Next.js, Jekyll, SvelteKit, Nuxt official documentation for generator-specific defaults.
  • Netlify, Vercel, Cloudflare Pages deployment docs for adapter and publish-dir requirements.
  • Nuxt end-of-life announcement for Nuxt 2 dated June 30, 2024.
  • Next.js source map policy per next.config.js documentation.

This post is informational, not deployment-consulting advice. Static-site generators evolve quickly; verify against the current version's documentation before applying any configuration change. Always test on a staging deploy before cutting over production.

← Back to Blog

Accessibility Options

Text Size
High Contrast
Reduce Motion
Reading Guide
Link Highlighting
Accessibility Statement

J.A. Watte is committed to ensuring digital accessibility for people with disabilities. This site conforms to WCAG 2.1 and 2.2 Level AA guidelines.

Measures Taken

  • Semantic HTML with proper heading hierarchy
  • ARIA labels and roles for interactive components
  • Color contrast ratios meeting WCAG AA (4.5:1)
  • Full keyboard navigation support
  • Skip navigation link
  • Visible focus indicators (3:1 contrast)
  • 44px minimum touch/click targets
  • Dark/light theme with system preference detection
  • Responsive design for all devices
  • Reduced motion support (CSS + toggle)
  • Text size customization (14px–20px)
  • Print stylesheet

Feedback

Contact: jwatte.com/contact

Full Accessibility StatementPrivacy Policy

Last updated: April 2026