Netlify Support Request: Static Assets Not Deploying (404 Errors)
Problem Summary
Site URL: https://perfectlyhired.com
Issue: All _next/static/* assets (CSS, JavaScript, fonts) return 404 errors on production deployment, while the site works correctly locally and on Netlify preview deployments.
Symptom: Deployment logs show only “4 new file(s) to upload” when there should be 50+ static files (55 files verified locally: 46 JS files, 1 CSS file, 7 font files, plus manifest files).
Impact: Site renders without any styling, JavaScript functionality, or fonts on production domain.
Technical Details
Build Configuration
- Next.js Version: 16.0.10
- @netlify**/plugin-nextjs Version**: 5.15.2 (latest)
- Node Version: 20.12.2
- Build Mode: SSR (Server-Side Rendering, NOT static export)
- Build Command:
npm run generate-sitemap && npm run build
Local Build Verification 
- Static Assets Generated: 55 files in
.next/static/- 46 JavaScript files in
chunks/ - 1 CSS file:
chunks/c4026e3d31803ba7.css - 7 font files in
media/ - Manifest files (
_buildManifest.js,_ssgManifest.js, etc.)
- 46 JavaScript files in
- Total Size: ~1.35 MB
- Build Status:
Successful
Production Deployment Evidence
From latest deployment logs:
Starting to deploy site from ‘.next’
Calculating files to upload
4 new file(s) to upload
THIS IS THE PROBLEM
4 new function(s) to upload
**Expected**: 50+ files should be uploaded **Actual**: Only 4 files uploaded ### Example 404 URLs (All Return 404) - `https://perfectlyhired.com/_next/static/chunks/c4026e3d31803ba7.css` - `https://perfectlyhired.com/_next/static/chunks/185c2acb2763bace.js` - `https://perfectlyhired.com/_next/static/media/83afe278b6a6bb3c-s.p.3a6ba036.woff2` --- ## Current Configuration ### `netlify.toml` ```toml [build] command = "npm run generate-sitemap && npm run build" publish = ".next" [[plugins]] package = "@netlify/plugin-nextjs" [build.environment] NODE_VERSION = "20.12.2" NETLIFY_NEXT_SKEW_PROTECTION = "true" [functions] node_bundler = "esbuild" external_node_modules = ["node-fetch"] # HTTP Headers for SEO [[headers]] for = "/*" [headers.values] X-Robots-Tag = "index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" X-Frame-Options = "DENY" X-Content-Type-Options = "nosniff" # Ensure CSS and JS assets are served correctly [[headers]] for = "/_next/static/*" [headers.values] Cache-Control = "public, max-age=31536000, immutable" [[headers]] for = "/_next/static/css/*" [headers.values] Content-Type = "text/css" Cache-Control = "public, max-age=31536000, immutable" next.config.js
/** @type {import('next').NextConfig} */ const nextConfig = { pageExtensions: ['tsx', 'ts', 'jsx', 'js'], images: { domains: ['perfectlyhired.com'], }, async redirects() { return [ { source: '/perfectly-hired-ai-powered-role-creation', destination: '/ai-powered-job-description', permanent: true, }, { source: '/locations', destination: '/', permanent: true, }, ]; }, async rewrites() { return [ { source: '/recruitment-service/hire-:slug(.*)', destination: '/recruitment-service/hire/:slug', }, ]; }, async headers() { return [ { source: '/(.*)', headers: [ { key: 'X-Frame-Options', value: 'DENY', }, { key: 'X-Content-Type-Options', value: 'nosniff', }, { key: 'X-Robots-Tag', value: 'index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1', }, ], }, ]; }, }; module.exports = nextConfig; middleware.ts
import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; const SKIP_PREFIXES = [ '/_next', '/api', '/favicon.ico', '/robots.txt', '/sitemap.xml', '/icons', '/images', ]; export function middleware(request: NextRequest) { const pathname = request.nextUrl.pathname; if (SKIP_PREFIXES.some(prefix => pathname.startsWith(prefix))) { return NextResponse.next(); } return NextResponse.next(); } export const config = { matcher: [ '/((?!api|_next/static|_next/image|favicon.ico).*)', ], }; public/_redirects
- Contains 5,000+ specific redirect rules
- No catch-all redirects (commented out:
# /* /index.html 200) - No redirects matching
/_next/*paths - All redirects are specific URL-to-URL mappings
.netlifyignore
# Dependencies node_modules/ # Source files (not needed in deployment) src/ scripts/ archive/ *.backup *.md !README.md # Config and dev files .git/ .vscode/ .idea/ *.log .env .env.local .env.*.local # Test files **/*.test.* **/*.spec.* # Note: .next/ is needed for @netlify/plugin-nextjs, so we don't exclude it Everything We’ve Tried
Configuration Changes
- Explicit publish directory: Set
publish = ".next"innetlify.toml - Removed publish directory: Tried letting plugin handle it automatically
- Removed redirects from netlify.toml: Moved all redirects to
public/_redirectsto avoid conflicts - Updated middleware: Explicitly configured to skip
/_nextpaths - Added skew protection:
NETLIFY_NEXT_SKEW_PROTECTION = "true"
Plugin Configuration
- Verified plugin version: Using latest
@netlify/plugin-nextjs@5.15.2 - Explicit plugin declaration: Plugin is explicitly listed in
netlify.tomlandpackage.json - Removed invalid plugin inputs: Removed
incrementalSourceBundling(not supported in v5.15.2)
Build Verification
- Local build check: Confirmed 55 static assets are generated correctly
- Post-build verification script: Created diagnostic script that confirms
.next/static/contains all files - Build logs analysis: Build completes successfully, all files exist in
.next/static/
Netlify UI Checks
- Publish directory: Verified in Netlify UI (set to
.nextor empty) - Build command: Matches
netlify.tomlconfiguration - Environment variables: Verified
BUILD_HOOK_URLandGOOGLE_SHEETS_WEBHOOK(unrelated to static assets)
Attempted Solutions That Didn’t Work
- Standalone build mode: Tried
output: 'standalone'innext.config.js- Plugin explicitly failed with error: “Your publish directory does not contain expected Next.js build output” - Manual static asset copy: Attempted copying
.next/statictopublic/_next/static- Failed because Next.js reserves/_nextroute and conflicts withpublic/_nextdirectory - Removing publish directory: Tried removing
publishfromnetlify.toml- No change, still only 4 files - Adding plugin inputs: Tried
incrementalSourceBundling = false- Plugin doesn’t accept this input in v5.15.2
Everything We Suspected (But Wasn’t the Issue)
Redirects Interference
Suspicion: Redirects in netlify.toml or public/_redirects might be catching /_next/static/* requests
Investigation:
- Checked all redirects - none match
/_next/*patterns - Removed redirects from
netlify.toml- no change - Verified catch-all redirect is commented out
- Conclusion: Not the issue
Middleware Interference
Suspicion: Middleware might be intercepting static asset requests
Investigation:
- Updated middleware to explicitly skip
/_nextpaths - Added matcher config to exclude static assets
- Conclusion: Not the issue (middleware correctly configured)
Publish Directory Configuration
Suspicion: Wrong publish directory or plugin not finding .next/static
Investigation:
- Tried explicit
publish = ".next" - Tried removing publish directory (let plugin handle it)
- Verified
.next/static/exists with 55 files after build - Conclusion: Not the issue (directory exists, plugin should find it)
Plugin Version
Suspicion: Outdated plugin version might have bugs
Investigation:
- Currently using
5.15.2(latest version) - Checked npm registry - no newer version available
- Conclusion: Not the issue (using latest version)
CSS File Location
Suspicion: CSS files in wrong location (e.g., public/ directory)
Investigation:
- CSS is correctly located in
app/globals.css - No CSS modules in
public/directory - CSS is properly imported in
app/layout.tsx - Conclusion: Not the issue (correct setup)
.gitignore Excluding Files
Suspicion: .next/ in .gitignore might prevent deployment
Investigation:
.next/is correctly gitignored (standard practice)- Netlify builds generate
.next/during build process - Build logs confirm
.next/static/exists after build - Conclusion: Not the issue (normal and expected)
Environment Variables
Suspicion: Missing or incorrect environment variables
Investigation:
NETLIFY_NEXT_SKEW_PROTECTION = "true"is setBUILD_HOOK_URLexists (unrelated to static assets)NODE_VERSION = "20.12.2"is set- Conclusion: Not the issue (correctly configured)
.netlifyignore Excluding Files
Suspicion: .netlifyignore might be excluding .next/static/
Investigation:
.netlifyignoreexplicitly does NOT exclude.next/- Comment in file confirms: “Note: .next/ is needed for @netlify/plugin-nextjs”
- Conclusion: Not the issue
Next.js Configuration
Suspicion: next.config.js might have incorrect settings
Investigation:
- No
output: 'export'(correct for SSR) - No
output: 'standalone'(we tried this, plugin doesn’t support it) - Standard SSR configuration
- Conclusion: Not the issue (correct configuration)
Build Cache Issues
Suspicion: Stale build cache causing issues
Investigation:
- Cleared Netlify cache multiple times
- Verified fresh builds generate correct files
- Conclusion: Not the issue (cache cleared, problem persists)
What We Need Help With
-
Why is the plugin only uploading 4 files instead of 55+?
- The build generates all files correctly
- The plugin should automatically package
.next/static/ - What is the plugin actually seeing/processing?
-
Is this a known bug with Next.js 16 and plugin v5.15.2?
- Are there compatibility issues?
- Are there workarounds or fixes available?
-
How can we verify what the plugin is processing?
- Can we get more detailed logs from the plugin?
- What files is it actually finding in
.next/static/?
-
Is there a configuration we’re missing?
- Are there required environment variables?
- Are there plugin inputs we should be using?
-
Why do preview deployments work but production doesn’t?
- Same build process
- Same plugin version
- Different behavior between preview and production
Additional Information
Deployment Logs (Key Excerpts)
✅ Build completed successfully ✅ "Starting to deploy site from '.next'" ⚠️ "4 new file(s) to upload" - This is suspiciously low! ✅ Functions bundled correctly ✅ Site deployed successfully Preview vs Production
- Preview deployments: Static assets work correctly
- Production deployment: Static assets return 404
- Same build process: Identical configuration and build command
Diagnostic Script Output (Local)
✅ Verified 55 static assets exist in .next/static → These should be deployed by Netlify from .next directory Additional Files (If Needed)
Here are the configuration files:
Configuration Files
netlify.toml
[build] command = "npm run generate-sitemap && npm run build" # Explicitly set publish directory - plugin will process .next and deploy static assets publish = ".next" [[plugins]] package = "@netlify/plugin-nextjs" [build.environment] NODE_VERSION = "20.12.2" # Enable skew protection to prevent 404s for static assets NETLIFY_NEXT_SKEW_PROTECTION = "true" [functions] node_bundler = "esbuild" external_node_modules = ["node-fetch"] # HTTP Headers for SEO [[headers]] for = "/*" [headers.values] X-Robots-Tag = "index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" X-Frame-Options = "DENY" X-Content-Type-Options = "nosniff" # Ensure CSS and JS assets are served correctly [[headers]] for = "/_next/static/*" [headers.values] Cache-Control = "public, max-age=31536000, immutable" [[headers]] for = "/_next/static/css/*" [headers.values] Content-Type = "text/css" Cache-Control = "public, max-age=31536000, immutable" # Note: _next/static/* assets are automatically handled by @netlify/plugin-nextjs # Do not add redirects for _next/* as they interfere with the plugin # Redirect blog tag pages to knowledge hub (handled in public/_redirects instead) # Temporarily removed from netlify.toml to test if redirects interfere with static assets # [[redirects]] # from = "/blog/tag/*" # to = "/knowledge-hub" # status = 301 # force = true next.config.js
/** @type {import('next').NextConfig} */ const nextConfig = { // Explicitly use App Router only - ignore pages directory pageExtensions: ['tsx', 'ts', 'jsx', 'js'], images: { domains: ['perfectlyhired.com'], }, async redirects() { return [ { source: '/perfectly-hired-ai-powered-role-creation', destination: '/ai-powered-job-description', permanent: true, }, { source: '/locations', destination: '/', permanent: true, }, ]; }, async rewrites() { return [ { source: '/recruitment-service/hire-:slug(.*)', destination: '/recruitment-service/hire/:slug', }, ]; }, async headers() { return [ { source: '/(.*)', headers: [ { key: 'X-Frame-Options', value: 'DENY', }, { key: 'X-Content-Type-Options', value: 'nosniff', }, { key: 'X-Robots-Tag', value: 'index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1', }, ], }, ]; }, }; module.exports = nextConfig; package.json (Relevant Sections)
{ "name": "perfect-hire-landing-page", "version": "0.0.0", "private": true, "scripts": { "dev": "next dev", "build": "next build", "postbuild": "node scripts/copy-static-assets.js", "start": "next start", "lint": "next lint", "generate-sitemap": "node scripts/generate-sitemap.mjs", "generate-blog-index": "node scripts/generateBlogIndex.js", "diagnose": "node scripts/diagnose-build.js" }, "dependencies": { "@netlify/functions": "^4.1.5", "next": "^16.0.10", "react": "^18.3.1", "react-dom": "^18.3.1", "node-fetch": "^3.3.2" }, "devDependencies": { "@netlify/plugin-nextjs": "^5.15.2", "typescript": "^5.5.3" } } middleware.ts
import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; // Skip middleware for static assets and API routes const SKIP_PREFIXES = [ '/_next', '/api', '/favicon.ico', '/robots.txt', '/sitemap.xml', '/icons', '/images', ]; export function middleware(request: NextRequest) { const pathname = request.nextUrl.pathname; // Skip middleware for static assets if (SKIP_PREFIXES.some(prefix => pathname.startsWith(prefix))) { return NextResponse.next(); } // All redirects are handled by Netlify _redirects file return NextResponse.next(); } export const config = { matcher: [ /* * Match all request paths except for the ones starting with: * - api (API routes) * - _next/static (static files) * - _next/image (image optimization files) * - favicon.ico (favicon file) */ '/((?!api|_next/static|_next/image|favicon.ico).*)', ], };