Rickhomes
Rickhomes

Reputation: 165

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

Upvotes: 4

Views: 4844

Answers (2)

M Zulfi Fahreza
M Zulfi Fahreza

Reputation: 101

try with this, set the custom js and css

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // swagger setup
  const config = new DocumentBuilder()
    .setTitle('Backend Generator')
    .setDescription('Documentation API Test')
    .setVersion('1.0')
    .setBasePath('api/v1')
    .addBearerAuth({ type: 'http', scheme: 'bearer', bearerFormat: 'JWT' })
    .build();

  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('swagger', app, document, {
    customSiteTitle: 'Backend Generator',
    customfavIcon: 'https://avatars.githubusercontent.com/u/6936373?s=200&v=4',
    customJs: [
      'https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.15.5/swagger-ui-bundle.min.js',
      'https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.15.5/swagger-ui-standalone-preset.min.js',
    ],
    customCssUrl: [
      'https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.15.5/swagger-ui.min.css',
      'https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.15.5/swagger-ui-standalone-preset.min.css',
      'https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.15.5/swagger-ui.css',
    ],
  });
  const cors = { ...CorsConfig };
  app.enableCors(cors);
  app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
  app.setGlobalPrefix('api/v1');
  useContainer(app.select(AppModule), { fallbackOnErrors: true });

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

Upvotes: 10

Rilla
Rilla

Reputation: 534

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

Upvotes: 5

Related Questions