Search code examples
node.jsswaggernestjsswagger-ui

Nestjs Swagger css not loading when deployed to vercel


Nestjs swagger ui not loading styles when deployed to vercel but works well locally

enter image description here

console and network requests enter image description here

enter image description here

I added vercel.json with the following configuration and deployed to vercel.

{
  "version": 2,
  "builds": [
    {
      "src": "src/main.ts",
      "use": "@vercel/node"
    }
  ],
  "routes": [
    {
      "src": "/(.*)",
      "dest": "src/main.ts",
      "methods": ["GET", "POST", "PUT", "PATCH", "DELETE"]
    }
  ]
}

main.ts

const swaggerConfig = new DocumentBuilder()
  .setTitle('Tansfun')
  .setDescription('API for Tansfun')
  .setVersion('1.0')

  .addBearerAuth(
    {
      type: 'http',
      scheme: 'bearer',
      bearerFormat: 'APIKey',
      name: 'APIKey',
      description: 'Enter API Key',
      in: 'header',
    },
    'APIKey-auth', 
  )
  .build();

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const document = SwaggerModule.createDocument(app, swaggerConfig);
  app.useGlobalPipes(new ValidationPipe());

  SwaggerModule.setup('api', app, document);

  await app.listen(port);
}
bootstrap();
 

I used @nestjs/swagger v6


Solution

  • I recently came across this issue. Fortunately I found a working working solution The solution is a bit hacky tho

    The First Solution

    Is to get you api's swagger JSON file host it and use it with a swagger ui explorer

    • Serve your swagger JSON file statically with nestjs
    • Get the path to the swagger JSON file on your vercel server
    • Use it with a swagger ui explorer

    How To Achieve Solution 1

    Steps

    • On your local machine / development machine set the NODE_ENV variable to development.

    In your .env file

    NODE_ENV="development"
    
    • Create a static folder in your projects root. eg: swagger-static

    • Statically serve content of the swagger-static folder here is link to documentation on serving static files with nestjs

    In your app.module.ts

    import { Module } from '@nestjs/common';
    import { AppController } from './app.controller';
    import { AppService } from './app.service';
    import { ServeStaticModule } from '@nestjs/serve-static';
    import { join } from 'path';
    
    @Module({
      imports: [
        ServeStaticModule.forRoot({
          rootPath: join(__dirname, '..', 'swagger-static'),
          serveRoot: process.env.NODE_ENV === 'development' ? '/' : '/swagger',
        }),
       ],
       controllers: [AppController],
       providers: [AppService],
     })
    
    export class AppModule {}
    
    • Every time your app starts in development you then to generate your api s swagger json and store in a swagger.json file in the swagger-static folder in your prjects root directory

    This issue on github discusses and has a solution on how to generate a swagger JSON file for your api

    Below is a code snippet on how to generate the swagger.json file

    In your main.ts

    import { NestFactory } from '@nestjs/core';
    import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
    import { AppModule } from './app.module';
    import { resolve } from 'path';
    import { writeFileSync } from 'fs';
    
    async function bootstrap() {
      const app = await NestFactory.create(AppModule);
    
      const options = new DocumentBuilder()
        .setTitle('Cats example')
        .setDescription('The cats API description')
        .setVersion('1.0')
        .addTag('cats')
        .build();
      const document = SwaggerModule.createDocument(app, options);
      SwaggerModule.setup('/swagger', app, document);
    
      await app.listen(process.env.PORT || 3000);
    
      // get the swagger json file (if app is running in development mode)
      if (process.env.NODE_ENV === 'development') {
        const pathToSwaggerStaticFolder = resolve(process.cwd(), 'swagger-static');
    
        // write swagger json file
        const pathToSwaggerJson = resolve(
          pathToSwaggerStaticFolder,
          'swagger.json',
        );
        const swaggerJson = JSON.stringify(document, null, 2);
        writeFileSync(pathToSwaggerJson, swaggerJson);
        console.log(`Swagger JSON file written to: '/swagger-static/swagger.json'`);
      }
    }
    
    bootstrap();
    
    • Now every time your app starts in development the swagger JSON file with the generated

    • In production it would be served on your vercel domain eg: https://yourprojectname.vercel.app/swagger/swagger.json

    • Push you changes to vercel and test your swagger api by using the path to the swagger.json file on your server

    • Eg: Head to the swagger ui explorer page https://petstore.swagger.io/?_ga=2.160760958.2144886769.1670328433-858019792.1670328433#/. On the the page enter the path to your swagger.json file in the explorer input and click explore. Your swagger docs your now be loaded

    The Second Solution (recommend)

    Is to get missing swagger files in development and manually serve them statically on vercel (your production serve)

    How To Achieve Solution 2

    Steps

    • On your local machine / development machine set the NODE_ENV variable to development.

    In your .env file

    NODE_ENV="development"
    
    • Create a static folder in your projects root. eg: swagger-static

    • Statically serve content of the swagger-static folder here is link to documentation on serving static files with nestjs

    In your app.module.ts

    import { Module } from '@nestjs/common';
    import { AppController } from './app.controller';
    import { AppService } from './app.service';
    import { ServeStaticModule } from '@nestjs/serve-static';
    import { join } from 'path';
    
    @Module({
      imports: [
        ServeStaticModule.forRoot({
          rootPath: join(__dirname, '..', 'swagger-static'),
          serveRoot: process.env.NODE_ENV === 'development' ? '/' : '/swagger',
        }),
       ],
       controllers: [AppController],
       providers: [AppService],
     })
    
    export class AppModule {}
    
    • Every time your app starts in development you make and http call to fetch the missing swagger ui resources on your production server. In my case the missing files were swagger-ui-bundle.js, swagger-ui-init.js, swagger-ui-standalone-preset.js and swagger-ui.css

    • In your main.ts file after your app has started check if your app is in development an fetch the missing swagger resources then store them in the swagger-static folder in your root directory

       import { NestFactory } from '@nestjs/core';
       import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
       import { AppModule } from './app.module';
       // core
       import { resolve } from 'path';
       import { writeFileSync, createWriteStream } from 'fs';
       import { get } from 'http';
      
       async function bootstrap() {
         const app = await NestFactory.create(AppModule);
      
         const options = new DocumentBuilder()
           .setTitle('Cats example')
           .setDescription('The cats API description')
           .setVersion('1.0')
           .addTag('cats')
           .build();
         const document = SwaggerModule.createDocument(app, options);
         SwaggerModule.setup('/swagger', app, document);
      
         await app.listen(process.env.PORT || 3000);
      
         // get the swagger json file (if app is running in development mode)
         if (process.env.NODE_ENV === 'development') {
      
           // write swagger ui files
           get(
             `${serverUrl}/swagger/swagger-ui-bundle.js`, function 
             (response) {
               response.pipe(createWriteStream('swagger-static/swagger-ui-bundle.js'));
               console.log(
           `Swagger UI bundle file written to: '/swagger-static/swagger-ui-bundle.js'`,
         );
           });
      
           get(`${serverUrl}/swagger/swagger-ui-init.js`, function (response) {
             response.pipe(createWriteStream('swagger-static/swagger-ui-init.js'));
             console.log(
           `Swagger UI init file written to: '/swagger-static/swagger-ui-init.js'`,
         );
           });
      
           get(
         `${serverUrl}/swagger/swagger-ui-standalone-preset.js`,
         function (response) {
             response.pipe(
             createWriteStream('swagger-static/swagger-ui-standalone-preset.js'),
           );
             console.log(
             `Swagger UI standalone preset file written to: '/swagger-static/swagger-ui-standalone-preset.js'`,
           );
           });
      
           get(`${serverUrl}/swagger/swagger-ui.css`, function (response) {
             response.pipe(createWriteStream('swagger-static/swagger-ui.css'));
             console.log(
           `Swagger UI css file written to: '/swagger-static/swagger-ui.css'`,
         );
           });
      
         }
       }
      
       bootstrap();
      
    • Now every time your app starts in development the missing swagger would be fetched localy and stored in the swagger-static folder

    • In production file missing would be served upon request on your vercel server

    • Push you changes to vercel and test your swagger. Everything should be working now