node.js - Angular 通用集 404 状态代码不起作用

标签 node.js angular express angular-universal

此时我别无选择。我们有一个 angular 11 网站,我们需要使用 Universal 进行 SEO。 我们有一个 404 页面,如果找不到该页面,我们会在该页面重定向。

问题是重定向返回状态代码 200 而不是 404,因此我们使用 Universal 来解决这个问题。 我让 Universal 工作并为该应用程序提供服务,但当重定向到 404 组件时,我没有让它抛出 404。

服务器.ts

import 'zone.js/dist/zone-node';
import { ngExpressEngine } from '@nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';

import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '@angular/common';
import { existsSync } from 'fs';
import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens';
import { NgxRequest, NgxResponse } from '@gorniv/ngx-universal';
import * as compression from 'compression';
import * as cookieparser from 'cookie-parser';
import { exit } from 'process';
import 'localstorage-polyfill';
import { enableProdMode } from '@angular/core';

// for debug
require('source-map-support').install();

// for tests
const test = process.env['TEST'] === 'true';

// ssr DOM
const domino = require('domino');
const fs = require('fs');
const path = require('path');
// index from browser build!
const template = fs.readFileSync(path.join('.', 'dist', 'index.html')).toString();
// for mock global window by domino
const win = domino.createWindow(template);
// mock
global['window'] = win;
global['localStorage'] = localStorage;
// not implemented property and functions
Object.defineProperty(win.document.body.style, 'transform', {
  value: () => {
    return {
      enumerable: true,
      configurable: true,
    };
  },
});
// mock documnet
global['document'] = win.document;
// othres mock
global['CSS'] = null;
// global['XMLHttpRequest'] = require('xmlhttprequest').XMLHttpRequest;
global['Prism'] = null;

// The Express app is exported so that it can be used by serverless Functions.
export function app() {
  // Faster server renders w/ Prod mode (dev mode never needed)
  enableProdMode();

  const server = express();
  const distFolder = join(process.cwd(), 'dist');
  const indexHtml = existsSync(join(distFolder, 'index.original.html'))
    ? 'index.original.html'
    : 'index';

  // redirects!
  const redirectowww = false;
  const redirectohttps = false;
  const wwwredirecto = true;
  server.use((req, res, next) => {
    // for domain/index.html
    if (req.url === '/index.html') {
      res.redirect(301, 'https://' + req.hostname);
    }
 
    // check if it is a secure (https) request
    // if not redirect to the equivalent https url
    if (
      redirectohttps &&
      req.headers['x-forwarded-proto'] !== 'https' &&
      req.hostname !== 'localhost'
    ) {
      // special for robots.txt
      if (req.url === '/robots.txt') {
        next();
        return;
      }
      res.redirect(301, 'https://' + req.hostname + req.url);
    }

    // www or not
    if (redirectowww && !req.hostname.startsWith('www.')) {
       res.redirect(301, 'https://www.' + req.hostname + req.url);
    }

    // www or not
    if (wwwredirecto && req.hostname.startsWith('www.')) {
      const host = req.hostname.slice(4, req.hostname.length);
      res.redirect(301, 'https://' + host + req.url);
    }

    // for test
    if (test && req.url === '/test/exit') {
       res.send('exit');
       exit(0);
    }

    next();
  });

  // Our Universal express-engine (found @ 
https://github.com/angular/universal/tree/master/modules/express-engine)
  server.engine('html', ngExpressEngine({
      bootstrap: AppServerModule
    })
 );

 server.set('view engine', 'html');
 server.set('views', distFolder);

 // Example Express Rest API endpoints
 // server.get('/api/**', (req, res) => { });
 // Serve static files from /browser
 server.get('*.*', express.static(distFolder, {
     maxAge: '1y',
 }),
);

server.get('/404', (req, res) => {
   res.status(404);
   res.render('index', { req });
});

// All regular routes use the Universal engine
server.get('*', (req, res) => {
 global['navigator'] = { userAgent: req['headers']['user-agent'] } as Navigator;
 const http = req.headers['x-forwarded-proto'] === undefined ? 'http' : req.headers['x-forwarded-proto'];

res.render(indexHtml, { req, res, bootstrap: AppServerModule, providers: [
    { 
      provide: APP_BASE_HREF, 
      useValue: req.baseUrl 
    },
    // for http and cookies
    {
      provide: REQUEST,
      useValue: req,
    },
    {
      provide: RESPONSE,
      useValue: res,
    },
    /// for cookie
    {
      provide: NgxRequest,
      useValue: req,
    },
    {
      provide: NgxResponse,
      useValue: res,
    },
    // for absolute path
    {
      provide: 'ORIGIN_URL',
      useValue: `${http}://${req.headers.host}`,
    },
  ],
 });
});

  return server;
}

function run() {
  const port = 4201;

  // Start up the Node server
  const server = app();
  // gzip
  server.use(compression());
  // cookies
  server.use(cookieparser());

  server.listen(port, () => {
    console.log(`Node Express server listening on http://localhost:${port}`);
  });
}

// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = (mainModule && mainModule.filename) || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
  run();
}

export * from './src/main.server';

app.browser.module

import { NgModule } from '@angular/core';
import { BrowserTransferStateModule } from '@angular/platform-browser';

import { REQUEST } from '@nguniversal/express-engine/tokens';

import { TranslatesBrowserModule } from '@shared/translates/translates-browser';

import { AppComponent } from './app.component';
import { AppModule } from './app.module';
import { ServiceWorkerModule } from '@angular/service-worker';
import { InlineStyleModule } from './inline-style/inline-style.module';
import { InlineStyleComponent } from './inline-style/inline-style.component';
import { StateTransferInitializerModule } from '@nguniversal/common';

// the Request object only lives on the server
export function getRequest(): any {
  return { headers: { cookie: document.cookie } };
}

export function getResponse(): any {
  return Response;
}

@NgModule({
  bootstrap: [AppComponent, InlineStyleComponent],
  imports: [
    AppModule,
    StateTransferInitializerModule,
    BrowserTransferStateModule,
    TranslatesBrowserModule,
    InlineStyleModule,
    ServiceWorkerModule.register('/ngsw-worker.js', { enabled: false }),
  ],
  providers: [
    {
      // The server provides these in main.server
      provide: REQUEST,
      useFactory: getRequest,
    },
    // {
    //   provide: RESPONSE,
    //   useValue: { status: function() {} }
    // },
    { provide: 'ORIGIN_URL', useValue: location.origin },
  ],
})
export class AppBrowserModule {}

错误页面.component.ts

import { Component, Input, OnInit, Inject, Optional } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Response } from 'express';
import { RESPONSE } from '@nguniversal/express-engine/tokens';
import { BaseComponent } from '../base.component';

@Component({
    selector: 'app-error-page',
    templateUrl: './error-page.component.html',
    styleUrls: ['./error-page.component.scss']
})
export class ErrorPageComponent extends BaseComponent implements OnInit {

  @Input() errorType: number;

    constructor(private route: ActivatedRoute, @Optional() @Inject(RESPONSE) private response: Response) { 
    super();
  }

    ngOnInit() {
    console.log('here with response', this.response);
    if (!!this.response)
      this.response.status(404);

    this.errorType = parseInt(this.route.snapshot.paramMap.get('type'));

        if (!this.errorType)
      this.errorType = 404;

    this.loadingService.showHideLoader(false);
  }
}

路由

const routes: Routes = [
  { path: '404', component: ErrorPageComponent },
  { path: '**', redirectTo: '404', pathMatch: 'full' }
];

export const AppRoutes = RouterModule.forRoot(routes, { initialNavigation: 'enabled' });

应用程序服务器模块

import { NgModule } from '@angular/core';
import { ServerModule, ServerTransferStateModule } from '@angular/platform-server';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';

import { TranslatesServerModule } from '@shared/translates/translates-server';

import { AppComponent } from './app.component';
import { AppModule } from './app.module';
import { InlineStyleComponent } from './inline-style/inline-style.component';
import { InlineStyleModule } from './inline-style/inline-style.module';
import { CookieService, CookieBackendService } from '@gorniv/ngx-universal';

@NgModule({
  imports: [
    // AppModule - FIRST!!!
    AppModule,
    ServerModule,
    NoopAnimationsModule,
    ServerTransferStateModule,
    InlineStyleModule,
    TranslatesServerModule
],
  bootstrap: [AppComponent, InlineStyleComponent],
  providers: [
    { provide: CookieService, useClass: CookieBackendService },
  ],
})
export class AppServerModule {}

应用程序模块.ts

BrowserModule.withServerTransition({ appId: 'my-app' }),

包.json

"dependencies": {
    "@angular/animations": "11.0.3",
    "@angular/cdk": "11.0.1",
    "@angular/common": "11.0.3",
    "@angular/compiler": "11.0.3",
    "@angular/core": "11.0.3",
    "@angular/forms": "11.0.3",
    "@angular/material": "11.0.1",
    "@angular/material-moment-adapter": "11.0.1",
    "@angular/platform-browser": "11.0.3",
    "@angular/platform-browser-dynamic": "11.0.3",
    "@angular/router": "11.0.3",
    "@angular/service-worker": "11.0.3",
    "@gorniv/ngx-universal": "2.2.2",
    "@ngneat/until-destroy": "8.0.3",
    "@nguniversal/common": "11.0.1",
    "@nguniversal/express-engine": "11.0.1",
    "@ngx-meta/core": "9.0.0",
    "@ngx-translate/core": "13.0.0",
    "@ngx-translate/http-loader": "6.0.0",
    "@types/googlemaps": "3.40.3",
    "@types/jquery": "^3.5.4",
    "classlist.js": "1.1.20150312",
    "cookie-parser": "1.4.5",
    "core-js": "3.8.1",
    "express": "4.17.1",
    "fingerprintjs2": "2.1.2",
    "jquery": "3.5.1",
    "localstorage-polyfill": "^1.0.1",
    "moment": "2.29.1",
    "node-cache": "5.1.2",
    "npm-run-all": "4.1.5",
    "reflect-metadata": "0.1.13",
    "rxjs": "6.6.3",
    "rxjs-compat": "6.6.3",
    "rxjs-tslint": "0.1.8",
    "save": "2.4.0",
    "toastify-js": "1.9.3",
    "tslib": "2.0.3",
    "vanilla-lazyload": "17.3.0",
    "web-animations-js": "2.3.2",
    "zone.js": "0.10.3"
},
"devDependencies": {
    "@angular-devkit/build-angular": "0.1100.2",
    "@angular/cli": "11.0.3",
    "@angular/compiler-cli": "11.0.3",
    "@angular/language-service": "11.0.3",
    "@angular/platform-server": "11.0.3",
    "@nguniversal/builders": "11.0.1",
    "@types/chai": "4.2.14",
    "@types/express": "4.17.9",
    "@types/jasmine": "3.6.2",
    "@types/jasminewd2": "2.0.8",
    "@types/node": "14.14.10",
    "chai": "4.2.0",
    "codelyzer": "6.0.1",
    "cross-env": "7.0.3",
    "mocha": "8.2.1",
    "nodemon": "2.0.6",
    "npm-delay": "1.0.4",
    "npm-run-all": "4.1.5",
    "prettier": "2.2.1",
    "prettier-tslint": "^0.2.0",
    "protractor": "7.0.0",
    "source-map-support": "0.5.19",
    "ssri": "8.0.0",
    "ts-mocha": "8.0.0",
    "ts-node": "9.1.0",
    "tslint": "6.1.3",
    "typescript": "4.0.5",
    "webpack-bundle-analyzer": "4.2.0",
    "webpack-cli": "3.3.11",
    "webpack-node-externals": "1.7.2"
}

我已经无计可施了,已经花了数周时间试图让这个工作现在没有任何成功

最佳答案

我认为问题可能出在提供者中,包括服务器和组件本身。这是一个简短的示例,说明我如何在一个通用项目中对文档实现设置状态 404:

server.ts

import 'zone.js/dist/zone-node';

import { ngExpressEngine } from '@nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';

import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '@angular/common';
import { existsSync } from 'fs';

import { NgxRequest, NgxResponse } from '@gorniv/ngx-universal';

// The Express app is exported so that it can be used by serverless Functions.
export function app() {
  const server = express();
  const distFolder = join(process.cwd(), 'dist/browser');
  const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';

  // Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
  server.engine('html', ngExpressEngine({
    bootstrap: AppServerModule,
  }));

  server.set('view engine', 'html');
  server.set('views', distFolder);

  // Example Express Rest API endpoints
  // server.get('/api/**', (req, res) => { });
  
  // Serve static files from /browser
  server.get('*.*', express.static(distFolder, {
    maxAge: '1y'
  }));

  // Do not serve the following routes via Universal
  server.get('/user/**', (req, res) => {
    res.sendFile(join(distFolder, 'index.html'));
  });

  // All regular routes use the Universal engine
  server.get('*', (req, res) => {
    res.render(indexHtml,
      {
        req: req,
        res: res,
        providers: [
          {
            provide: APP_BASE_HREF, useValue: req.baseUrl
          },
          {
            provide: NgxRequest, useValue: (req)
          },
          {
            provide: NgxResponse, useValue: (res)
          }
        ]
      });
  });

  return server;
}

function run() {
  const port = process.env.PORT || 5000;

  // Start up the Node server
  const server = app();
  server.listen(port, () => {
    console.log(`Node Express server listening on http://localhost:${port}`);
  });
}

// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = mainModule && mainModule.filename || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
  run();
}

export * from './src/main.server';

page-not-found.component.ts

import { isPlatformServer } from '@angular/common';
import { Component, Inject, Injector, OnInit, PLATFORM_ID } from '@angular/core';
import { RESPONSE } from '@nguniversal/express-engine/tokens';

@Component({
  selector: 'page-not-found',
  templateUrl: './page-not-found.component.html',
  styleUrls: ['./page-not-found.component.scss']
})
export class PageNotFoundComponent implements OnInit {

  constructor(
    @Inject(PLATFORM_ID) private platformId,
    private injector: Injector
  ) { }

  ngOnInit() {
    if (isPlatformServer(this.platformId)) {
      const response = this.injector.get(RESPONSE);
      response.status(404);
    }
  }

}

关于node.js - Angular 通用集 404 状态代码不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65195908/

相关文章:

javascript - Node.js 路由错误

javascript - 从 HTTP POST 请求中检索 JSON

node.js readline - 写入数据出现在输入中

javascript - 导入的数据库函数未在 Expressjs 中返回路由内的值

javascript - i18n 翻译在支持 Handlebars 的 Node Express 应用程序中部分工作

node.js - 在 NodeJS 中正确使用 'await' 和 'async'

angular - 正在调用的 spy 返回未通过 angular5 单元测试调用

angular - 无法直接访问 FormControl 实例。无法读取未定义的属性 'invalid'

javascript - 无法将数据修补到 FormArray

node.js - Memcache v/s redis 用于维护持久 session ?