Angular 7 Universal SSR部署Heroku遇document未定义错误致应用崩溃求助
ERROR ReferenceError: document is not defined & App Crashes Let's break down what's going on here and how to fix it—this is a super common pitfall with Angular SSR, especially when deploying to platforms like Heroku.
Why This Happens
Your isPlatformBrowser check is a good start, but two key issues are likely tripping you up:
- Early Injection of
DOCUMENT: If you’re directly injectingDOCUMENTinto your component (like@Inject(DOCUMENT) private document: Document), Angular tries to resolve this dependency before your platform check runs. On the server, there’s nodocumentobject, so this throws an error immediately—your conditional never even gets a chance to execute. - Uncaught Errors Causing Heroku Crashes: Every
ReferenceErrorpiles up in your logs, and uncaught exceptions will eventually take down your Node.js process on Heroku. That’s why you’re seeing the "Application error" after some time.
Step-by-Step Fixes
1. Safe Injection of DOCUMENT
Instead of injecting DOCUMENT directly, create a factory provider that returns a safe mock for the server. Add this to your app’s root providers (e.g., app.module.ts or app.server.module.ts):
import { NgModule } from '@angular/core'; import { PLATFORM_ID, DOCUMENT } from '@angular/common'; import { isPlatformBrowser } from '@angular/common'; @NgModule({ providers: [ { provide: DOCUMENT, useFactory: (platformId: Object) => { // Return real document if in browser, else a harmless mock return isPlatformBrowser(platformId) ? document : {} as Document; }, deps: [PLATFORM_ID] } ] }) export class AppModule {}
This ensures the server gets a mock object instead of trying to access a non-existent document.
2. Add DOM Simulation to Server-Side Code
To fully avoid document-related errors during SSR, use domino to mock the browser’s DOM environment in your server.ts file. First install the package:
npm install domino --save-dev
Then update server.ts:
import 'zone.js/dist/zone-node'; import { ngExpressEngine } from '@nguniversal/express-engine'; import * as express from 'express'; import { join } from 'path'; const domino = require('domino'); const fs = require('fs'); // Load browser index.html to create a mock window const template = fs.readFileSync(join(process.cwd(), 'dist/browser/index.html')).toString(); const win = domino.createWindow(template); // Mock browser globals for server-side rendering global['window'] = win; global['document'] = win.document; global['navigator'] = win.navigator; // Rest of your server.ts code...
This gives the server a fake DOM to work with, so any accidental access to document won’t throw errors.
3. Guard Document Operations Properly
Even with the above fixes, make sure your document-related code is wrapped both in the platform check and only runs after the component has initialized in the browser. Move your style-setting code to ngAfterViewInit (which only runs when the DOM is ready):
import { Component, AfterViewInit, PLATFORM_ID, Inject, Renderer2 } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; @Component({ selector: 'app-your-component', templateUrl: './your-component.component.html' }) export class YourComponent implements AfterViewInit { isMenuOpened = false; constructor( @Inject(PLATFORM_ID) private platformId: Object, private renderer: Renderer2, @Inject(DOCUMENT) private document: Document ) {} ngAfterViewInit(): void { if (isPlatformBrowser(this.platformId)) { const overflow = this.isMenuOpened ? 'hidden' : 'auto'; this.renderer.setStyle(this.document.body, 'overflow', overflow); } } }
4. Prevent Uncaught Exceptions from Crashing Heroku
Add error handlers to your server.ts to catch uncaught exceptions and keep your process running (or exit gracefully):
// At the top of server.ts, after imports process.on('uncaughtException', (err) => { console.error('Uncaught Exception:', err); // Optional: Exit gracefully if needed // process.exit(1); }); process.on('unhandledRejection', (reason, promise) => { console.error('Unhandled Rejection at:', promise, 'Reason:', reason); });
This stops minor errors from snowballing into a full app crash on Heroku.
5. Verify Your Build Configuration
Double-check your webpack.server.config.js to ensure it’s properly excluding browser-only dependencies. Make sure it has something like:
module.exports = { target: 'node', externals: [ // Exclude node modules that don't work in Node.js /node_modules/, ], // Rest of your config... };
Final Checks
After making these changes:
- Test SSR locally first with
npm run build && npm startto confirm no document errors appear in your logs. - Deploy to Heroku again, then monitor logs with
heroku logs --tailto verify the errors are gone. - If you’re using third-party libraries, ensure they’re SSR-compatible—some libraries might hardcode
documentaccess, so you may need to dynamically import them only in the browser.
内容的提问来源于stack exchange,提问作者Michael Kutateladze

