简体   繁体   中英

How to redirect all routes to index.html (Angular) in nest.js?

I am making Angular + NestJS app, and I want to send index.html file for all routes.

main.ts

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useStaticAssets(join(__dirname, '..', 'frontend', 'dist', 'my-app'));
  app.setBaseViewsDir(join(__dirname, '..', 'frontend', 'dist', 'my-app'));
  await app.listen(port);
}

app.controller.ts

@Controller('*')
export class AppController {

  @Get()
  @Render('index.html')
  root() {
    return {};
  }
}

It works fine while I open localhost:3000/ , but if I open localhost:3000/some_route the server falls with 500 internal error and says Can not find html module . I was searching why I am getting this error and everyone says set default view engine like ejs or pug , but I don't want to use some engines, I just want to send plain html built by angular without hacking like res.sendFile('path_to_file') . Please help

You can only use setBaseViewsDir and @Render() with a view engine like handlebars (hbs); for serving static files (Angular), however, you can only use useStaticAssets and response.sendFile .

To serve index.html from all other routes you have a couple of possibilities:

A) Middleware

You can create a middleware that does the redirect, see this article :

@Middleware()
export class FrontendMiddleware implements NestMiddleware {
  resolve(...args: any[]): ExpressMiddleware {
    return (req, res, next) => {
      res.sendFile(path.resolve('../frontend/dist/my-app/index.html')));
    };
  }
}

and then register the middleware for all routes:

export class ApplicationModule implements NestModule {
  configure(consumer: MiddlewaresConsumer): void {
    consumer.apply(FrontendMiddleware).forRoutes(
      {
        path: '/**', // For all routes
        method: RequestMethod.ALL, // For all methods
      },
    );
  }
}

B) Global Error Filter

You can redirect all NotFoundExceptions to your index.html :

@Catch(NotFoundException)
export class NotFoundExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    response.sendFile(path.resolve('../frontend/dist/my-app/index.html')));
  }
}

and then register it as a global filter in your main.ts :

app.useGlobalFilters(new NotFoundExceptionFilter());

Updated Answer for December 10, 2019

You need to create middleware for sending the react index.html

Create middleware file

frontend.middleware.ts

import { NestMiddleware, Injectable } from '@nestjs/common';
import {Request, Response} from "express"
import { resolve } from 'path';

@Injectable()
export class FrontendMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: Function) {
    res.sendFile(resolve('../../react/build/index.html'));
  }
}

Include middleware in

app.module.ts

import { FrontendMiddleware } from './frontend.middleware';
import {
  Module,
  MiddlewareConsumer,
  RequestMethod,
} from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {
  configure(frontEnd: MiddlewareConsumer) {
    frontEnd.apply(FrontendMiddleware).forRoutes({
      path: '/**', // For all routes
      method: RequestMethod.ALL, // For all methods
    });
  }
}

App Structure for Reference:

enter image description here

You can also use Cloud Functions for Firebase together with Firebase Hosting. What you have in main.ts is perfectly fine, with this approach you even don't need a controller. You should go as follows:

  1. Rename index.html to index2.html . This is important to render your route path, otherwise you will have rendering working fine on all routes, excluding the root / .
  2. Update angular.json to have the following "index": "apps/myapp/src/index2.html", (Simply change index.html to index2.html ). Note: path to the index.html might be different for you, I'm using Nx workspace .
  3. Add templatePath: join(BROWSER_DIR, 'index2.html'), to NestJS's ApplicationModule , most probably you name the file as app.module.ts in a server directory.

Like so:

@Module({
  imports: [
    AngularUniversalModule.forRoot({
      bundle: require('./path/to/server/main'), // Bundle is created dynamically during build process.
      liveReload: true,
      templatePath: join(BROWSER_DIR, 'index2.html'),
      viewsPath: BROWSER_DIR
    })
  ]
})
  1. Initialize Firebase Cloud Functions and Firebase Hosting, for how to set up this you can check https://hackernoon.com/deploying-angular-universal-v6-with-firebase-c86381ddd445 or https://blog.angularindepth.com/angular-5-universal-firebase-4c85a7d00862

  2. Edit your firebase.json .

It should look like that, or at least the hosting part.

{
  "hosting": {
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "public": "functions/dist/apps/path/to/browser",
    "rewrites": [
      {
        "function": "angularUniversalFunction",
        "source": "**"
      }
    ]
  }
} 
  1. In your main.ts you need to set up Cloud Functions on your server.

In a minimialistic case it would like something like that:

import * as admin from 'firebase-admin';
import * as functions from 'firebase-functions';

admin.initializeApp(); // Initialize Firebase SDK.
const expressApp: Express = express(); // Create Express instance.

// Create and init NestJS application based on Express instance.
(async () => {
  const nestApp = await NestFactory.create<NestExpressApplication>(
    ApplicationModule,
    new ExpressAdapter(expressApp)
  );
  nestApp.init();
})().catch(err => console.error(err));

// Firebase Cloud Function for Server Side Rendering (SSR).
exports.angularUniversalFunction = functions.https.onRequest(expressApp);

With this approach you don't have to care about routes on the NestJS side. You can set up everything on the Angular side, and that's all. Angular takes care for routing. As you probably noticed this is Server-Side Rendering (SSR), but redirection of all routes to index.html (or more precisely index2.html ) can be done using NestJS + Cloud Functions for Firebase in conjuction. Plus you have a SSR "for free" :)

Projects to showcase:

1) Angular + Angular Universal (SSR) + Cloud Functions for Firebase: https://github.com/Ismaestro/angular8-example-app (missing NestJS).

2) Angular + NestJS: https://github.com/kamilmysliwiec/universal-nest (missing Cloud Functions for Firebase).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM