使用Angular Universal服务端渲染时matchMedia无法正常工作
matchMedia Not Working in Angular Universal Hey there, let's tackle this matchMedia issue in Angular Universal! The root problem is straightforward: server-side rendering runs in a Node.js environment, which doesn't have access to browser-specific APIs like matchMedia. Here are two solid solutions to get things working again:
1. Use Angular CDK's BreakpointObserver (Official Recommended Approach)
Angular's Component Dev Kit (CDK) includes BreakpointObserver—a purpose-built, SSR-friendly tool for detecting media query changes. It’s the cleanest replacement for direct matchMedia calls in Angular apps.
Step 1: Install Angular CDK (if you haven’t already)
npm install @angular/cdk
Step 2: Refactor Your Component Code
Replace your existing matchMedia logic with BreakpointObserver like this:
import { Component, OnInit, ViewChild, ViewChildren, QueryList } from '@angular/core'; import { MatSidenav } from '@angular/material/sidenav'; import { ActivatedRoute, Router } from '@angular/router'; import { BreakpointObserver } from '@angular/cdk/layout'; export class BasicLayoutComponent implements OnInit { @ViewChild(MatSidenav) sidenav: MatSidenav; @ViewChildren('sidenav') sidenavs: QueryList<MatSidenav>; constructor( private _route: ActivatedRoute, private _router: Router, private breakpointObserver: BreakpointObserver ) {} ngOnInit() { // Watch for your small screen breakpoint this.breakpointObserver.observe(`(max-width: ${SMALL_WIDTH_BREAKPOINT}px)`).subscribe(result => { if (result.matches) { // Logic for small screens (e.g., close sidenav) this.sidenav.close(); } else { // Logic for large screens (e.g., open sidenav) this.sidenav.open(); } }); // Rest of your ngOnInit logic... this._router.events.subscribe(/* ... */); } }
This works flawlessly on both server and client because the CDK handles SSR environment differences internally.
2. Polyfill matchMedia for the Server (Alternative)
If you’d rather stick with matchMedia, you can add a polyfill to simulate browser behavior on the server, plus guard client-only code.
Step 1: Add the Polyfill to server.ts
At the top of your project’s server.ts file, add this to mock matchMedia:
// Polyfill matchMedia for SSR Object.defineProperty(global, 'matchMedia', { writable: true, value: (query: string) => ({ matches: false, // Default to non-mobile on server (adjust as needed) media: query, onchange: null, addListener: () => {}, removeListener: () => {}, addEventListener: () => {}, removeEventListener: () => {}, dispatchEvent: () => false, }), }); // Rest of your server.ts code...
Step 2: Guard Client-Side Code with isPlatformBrowser
Ensure matchMedia only runs in the browser using Angular’s platform utilities:
import { Component, OnInit, ViewChild, ViewChildren, QueryList, Inject, PLATFORM_ID } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; import { MatSidenav } from '@angular/material/sidenav'; import { ActivatedRoute, Router } from '@angular/router'; export class BasicLayoutComponent implements OnInit { private mediaMatcher: MediaQueryList | undefined; @ViewChild(MatSidenav) sidenav: MatSidenav; @ViewChildren('sidenav') sidenavs: QueryList<MatSidenav>; constructor( private _route: ActivatedRoute, private _router: Router, @Inject(PLATFORM_ID) private platformId: Object ) {} ngOnInit() { if (isPlatformBrowser(this.platformId)) { this.mediaMatcher = matchMedia(`(max-width: ${SMALL_WIDTH_BREAKPOINT}px)`); this.mediaMatcher.addEventListener('change', (mql) => { mql.matches ? this.sidenav.close() : this.sidenav.open(); }); } // Rest of your ngOnInit logic... this._router.events.subscribe(/* ... */); } }
Quick Tips
- Stick with
BreakpointObserverif possible—it’s designed for Angular, plays nice with change detection, and avoids manual environment checks. - Never call browser APIs directly in SSR without guarding them first—use Angular’s provided utilities to handle platform differences.
内容的提问来源于stack exchange,提问作者Ole Bille

