Skip to main content

Solving "sw.js 404 Not Found" Errors for PropellerAds Push Notifications

 You have integrated the PropellerAds script, deployed your application, and checked your browser console only to find a critical error: GET /sw.js 404 (Not Found). Consequently, your push notification revenue stream fails to initialize, and the ad network dashboard reports your zone as "Not Verified."

This is a ubiquitous issue in modern JavaScript development environments. While PropellerAds provides the sw.js file, modern bundlers like Webpack, Vite, or Parcel do not automatically place this file where the browser expects it.

This guide details the root cause of this scope error and provides a rigorous, architectural solution to correctly bundle and serve the Service Worker file using Webpack and standard PWA practices.

The Root Cause: Service Worker Scope and Bundling

To fix the 404 error, you must first understand the strict security model of Service Workers and how build pipelines disrupt it.

1. The Scope Constraint

A Service Worker can only control clients (pages) that are within its scope. The scope is determined by the location of the sw.js file on the server.

  • Incorrect: If sw.js is located at https://example.com/assets/js/sw.js, the scope is /assets/js/. It cannot control the root page.
  • Required: PropellerAds requires the file to be at https://example.com/sw.js so it can control the entire domain scope (/).

2. The Bundler Problem

Webpack and other build tools are designed to take your source code, hash the filenames for cache busting (e.g., main.a1b2c3.js), and output them into a nested directory like dist/static/js.

If you simply place sw.js in your src folder, Webpack will either:

  1. Ignore it completely (if it's not imported).
  2. Bundle it into the main JavaScript blob.
  3. Output it with a hash, making the filename unpredictable.

The browser requests /sw.js specifically. If your server or build output doesn't map that exact route to the file, you get a 404.

The Solution: Enforcing Root Placement with Webpack

The most robust solution involves explicitly instructing Webpack to take the sw.js file from your source and copy it—verbatim and unhashed—to the root of your build directory.

We will use the copy-webpack-plugin for this task.

Step 1: Install Dependencies

Ensure you have the plugin installed in your development dependencies.

npm install copy-webpack-plugin --save-dev

Step 2: Configure Webpack

Modify your webpack.config.js. You need to add the CopyPlugin to your plugins array. This configuration assumes your source file is located at src/sw.js and you want it output to dist/sw.js.

const path = require('path');
const CopyPlugin = require('copy-webpack-plugin');

module.exports = {
  // Your existing entry point
  entry: './src/index.js',
  
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js',
    clean: true, // Cleans the dist folder before build
  },

  plugins: [
    // ... other plugins like HtmlWebpackPlugin
    
    new CopyPlugin({
      patterns: [
        { 
          from: path.resolve(__dirname, "src/sw.js"), 
          to: "sw.js" // Forces the output filename to be exactly sw.js at the root
        },
      ],
    }),
  ],

  // crucial for ensuring the SW works across environments
  mode: 'production',
};

Step 3: Handling Framework Abstractions (Next.js / Create React App)

If you are not managing the Webpack config directly, you must use the framework's "Public" folder convention. Files in these folders are served statically from the root.

For Next.js: Move sw.js to: your-project/public/sw.js

For Create React App (CRA): Move sw.js to: your-project/public/sw.js

For Vite: Move sw.js to: your-project/public/sw.js

In these frameworks, the build process automatically copies the contents of public/ to the root of the build output. No extra Webpack configuration is required.

Correctly Registering the Service Worker

Even if the file exists at the correct path, your application logic must register it correctly. A common mistake is using relative paths that break when the user navigates to sub-routes (e.g., example.com/blog/post-1).

Use an absolute path (/sw.js) during registration.

// verification-script.js
// Place this logic in your main entry point (e.g., index.js or App.tsx)

const registerPropellerSW = async () => {
  if ('serviceWorker' in navigator) {
    try {
      // The leading slash is mandatory to reference the domain root
      const registration = await navigator.serviceWorker.register('/sw.js', {
        scope: '/'
      });

      console.log('PropellerAds SW registered with scope:', registration.scope);
    } catch (error) {
      console.error('PropellerAds SW registration failed:', error);
    }
  }
};

// Execute immediately
registerPropellerSW();

Server-Side Configuration (Nginx/Apache)

If the file is physically present in the build folder but you still receive a 404 or a MIME type error, your web server configuration may be at fault.

Service Workers require the HTTP header Content-Type: application/javascript.

Nginx Configuration

If you are hosting a Single Page Application (SPA), ensure your Nginx config allows direct access to sw.js instead of falling back to index.html.

server {
    listen 80;
    server_name example.com;
    root /var/www/html;

    # Explicitly serve the service worker
    location /sw.js {
        add_header Cache-Control "no-cache";
        default_type application/javascript;
    }

    # SPA Fallback for other routes
    location / {
        try_files $uri $uri/ /index.html;
    }
}

Apache Configuration (.htaccess)

Ensure rewrite rules do not redirect the service worker request.

<IfModule mod_rewrite.c>
  RewriteEngine On
  
  # Do not rewrite sw.js
  RewriteRule ^sw\.js$ - [L]
  
  # Standard SPA routing
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule . /index.html [L]
</IfModule>

Edge Cases and Common Pitfalls

1. HTTPS Requirement

Service Workers strictly require a secure context. They will fail silently or throw errors if served over HTTP. The only exception is localhost for debugging purposes. Ensure your production environment has a valid SSL certificate.

2. Caching Issues

Browsers handle Service Worker caching differently than standard assets. If you update the PropellerAds sw.js file, the browser might treat the old one as valid for up to 24 hours.

To force updates, configure your server to send the following headers specifically for sw.js:

  • Cache-Control: no-cache, no-store, must-revalidate
  • Pragma: no-cache
  • Expires: 0

3. Service Worker Conflicts

If you are building a PWA, you likely have your own service-worker.js (generated by Workbox or similar). A domain can have multiple service workers, but they compete for scope.

If PropellerAds mandates sw.js, it is safest to keep it separate. However, if you need to merge functionality, you can use importScripts inside your main Service Worker, provided the PropellerAds script is hosted on the same origin or has CORS enabled.

// inside your main-sw.js
importScripts('/sw.js'); 
// Note: This relies on the proprietary code inside sw.js being compatible
// with importScripts execution contexts.

Warning: Merging third-party ad scripts into your primary application Service Worker carries stability risks. Running them as separate registrations is preferred if the architecture allows.

Conclusion

The sw.js 404 error is rarely a defect in the PropellerAds platform; it is a deployment artifact caused by modern bundlers obscuring file paths. By explicitly using CopyWebpackPlugin in Webpack, or leveraging the public directory in modern frameworks, you ensure the Service Worker resides at the domain root. Combined with correct server-side MIME types and HTTPS, this ensures high availability for your push notification inventory.