Search code examples
javascriptnginxmime-typesnginx-configmime

MIME type issue with nginx and pdf files


So I wanted to create a web application in angular that, amongst other things, shows pdf files to the user. The pdf files are stored in a database, and are retreived by and sent to the user with a Spring Boot backend. When running on a local machine, everything was fine. However, issues arose once I wanted to run it in a ngnix based docker image. The error log keeps saying there's an issue with MIME types. Note: this is specifically for the part loading in the PDF, everything else seems to function just fine.

The Error log:

main-7X4J5JFT.js:50 Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "application/octet-stream". Strict MIME type checking is enforced for module scripts per HTML spec.

Now, I have tried a lot - and I mean A LOT - of different sulotions over a span of 3 full days. I might be missing something very important, but I just really don't get what I should change to my project. For reference, here is the nginx.conf:

server {
    listen 80;
    server_name _;

    root /usr/share/nginx/html;
    index index.html;

    location / {
        try_files $uri /index.html;
    }

    location ~* \.(?:ico|css|js|gif|jpe?g|png|woff2?|eot|ttf|svg|otf|webmanifest|txt|xml)$ {
        expires 6M;
        access_log off;
        add_header Cache-Control "public";
    }

    location ~* \.pdf$ {
            add_header Content-Type application/pdf;
            expires 1M;
            access_log off;
            add_header Cache-Control "public";
        }

    location ~ \.css {
        add_header  Content-Type    text/css;
    }
    location ~ \.js {
        add_header  Content-Type    application/x-javascript;
    }
}

This is my dockerfile:

# Step 1: Use a Node.js base image to build the app
FROM node:22.13.0-alpine AS build

# Set the working directory
WORKDIR /app

# Copy package.json and package-lock.json
COPY package.json package-lock.json ./

# install project dependencies
RUN npm install
RUN npx ngcc --properties es2025 browser module main --first-only --create-ivy-entry-points

# Copy the entire project to the container
COPY . .

# Build the Angular app
RUN npm run build

# Step 2: Use an NGINX base image to serve the app
FROM nginx:stable

# Copy built Angular app from the build stage to the NGINX HTML folder
COPY --from=build /app/dist/cyberlab-frontend/browser /usr/share/nginx/html
RUN chmod -R 755 /usr/share/nginx/html

# Copy custom Nginx configuration file
COPY nginx.conf /etc/nginx/conf.d/default.conf

# Expose the default NGINX port
EXPOSE 80

This is the part in the backend that handles the sending of the pdf file:

@RateLimiter(name = "default")
    @GetMapping("/fetchLab")
    public ResponseEntity<byte[]> getLab(@RequestParam long labId, @RequestParam String labName) {
        try {
            Optional<LabDTO> labDetails = this.labService.getLabById(labId);
            if (labDetails.isEmpty()) {
                throw new Exception("Could not find specified lab");
            }

            if (!labDetails.get().name().equals(labName)){
                throw new Exception("LabId and Labname do not match");
            }

            byte[] pdf = labDetails.get().pdf();
            String fileName = labDetails.get().name() + ".pdf";

            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_PDF);
            headers.setContentDisposition(ContentDisposition.inline().filename(fileName).build());

            return new ResponseEntity<>(pdf, headers, HttpStatus.OK);
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                    .body(String.format("{\"success\":false,\"message\":\"%s\"}", e.getMessage()).getBytes());
        }
    }

And last, the frontend method that should handle the viewer of the pdf file. Please note, in my HTML I am using a ngx-extended-pdf-viewer:

ngOnInit(): void {
    this.labId = this.route.snapshot.params['labId'];
    this.labName = this.route.snapshot.params['labName'];
    this.labService.fetchLab(this.labId!, this.labName!).subscribe({
      next: (file: Blob) => {
        console.log('Received Blob Size:', file.size); // Log Blob size
        if (file.size > 0) {
          this.pdfFile = URL.createObjectURL(file); // Pass this URL to your PDF viewer
        } else {
          window.alert('Received an empty PDF file');
        }
      },
      error: (err) => {
        console.error('Failed to fetch the lab:', err);
        window.alert('Page does not exist or an error occurred.');
      }
    });
  }

Solution

  • Re edit. You have named the library you are using. The issue you describe is the very first thing on its Troubleshooting page! It even gives you a copy/paste solution for nginx.


    You need to think less about the end goal and more about what the error message is telling you.

    main-7X4J5JFT.js:50

    So the error occurred in the file main-7X4J5JFT.js on line 50.

    Look at line 50 of that file and see what it is doing

    Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "application/octet-stream".

    So it tried to load a JavaScript module and the server responded with something which had Content-Type: application/octet-stream

    Look in the Network tab of your browser's developer tools. Find the request for that file. It will be one with that content-type. It will probably have its file name on line 50 of main-7X4J5JFT.js.


    Note that this has nothing to do with PDFs. It's all about loading your (presumably) third-party library.


    Given that I don't have access to the information you would get from doing that debugging, the rest of this answer is an informed guess.

    1. The file, being a module, has a .mjs file extension
    2. nginx doesn't have a Content-Type configured for that by default
    3. You can resolve the problem by configuring one in the same way you did for .js files