角度通用集合404状态代码不工作



我现在没有选择了。我们有一个棱角分明的11网站,我们需要使用通用搜索引擎优化。我们有一个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';

应用程序浏览器模块

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 {}

错误页面组件.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' });

app.server.module

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 {}

app.module.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设置为文档的:

服务器.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';

未找到页面。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);
}
}
}

相关内容

最新更新