Mastering Angular SSR: Enabling Custom HTTP Responses (404, 301)
Tags: seo, angular, ssr, howto
Published: 2024-05-08 06:26:22

Welcome to the first post in our series on optimizing Angular apps for search enginge optimisation (SEO)! In this post, we'll walk you through enabling your Angular app to return custom HTTP responses, such as 404 (Not Found) and 301 (Permanent Redirect), using Angular SSR.

## Why is This Important?
When optimizing your Angular app for SEO, it's crucial to provide the correct HTTP status codes. Here's why returning accurate 404 and 301 responses improves SEO over always returning a 200 status:

### Search Engine Understanding:

- **404 (Not Found)**: When search engines encounter a 404 status, they understand that the page does not exist. This prevents them from indexing non-existent content, which could lead to penalties for having "soft 404" pages (pages that return a 200 status but have no useful content).

- **301 (Permanent Redirect)**: Tells search engines that the requested URL has permanently moved to a new location. This consolidates SEO value to the new URL, ensuring that link equity (backlinks, page authority) is transferred correctly.

### Crawl Efficiency:
- By signaling the correct 404 status for non-existent pages, search engines won't waste crawl budget trying to index irrelevant pages.
- With a 301 status, search engines quickly find and index the new URL, improving the discoverability of updated content.

### Improved User Experience:
- While the user is still redirected internally or sees a custom 404 page, search engines receive accurate status codes that help them index the site correctly.
- Ensuring users land on useful, relevant content reduces bounce rates and increases engagement.
- By implementing 404 and 301 responses correctly, you ensure that search engines interpret your content accurately, improving your app's search visibility and overall user experience.


## Implementation Details
To enable custom HTTP responses (404, 301) in your Angular 17+ app with built-in SSR support, follow these steps:

### 1. Soft 404 Component
In your component that handles soft 404 pages, inject the Response object conditionally to ensure it's only used server-side:
```typescript
import { Response } from 'express';
import { CommonModule, isPlatformServer } from '@angular/common';
import { ChangeDetectionStrategy, Component, Inject, Optional, PLATFORM_ID, inject } from '@angular/core';

import { RESPONSE } from './server.token';

@Component({
    selector: 'app-soft-404',
    standalone: true,
    imports: [CommonModule],
    templateUrl: './soft-404.component.html',
    styleUrls: ['./soft-404.component.scss'],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class Soft404Component {
    platformId = inject(PLATFORM_ID);
    constructor(
        @Optional() @Inject(RESPONSE) private response: Response
    ) {
        // Only executes server-side
        if (isPlatformServer(this.platformId)) {
            this.response?.status(404);
        }
    }
}
```
### 2. Create a Response Token
Provide the Response token that you'll use for dependency injection (`server.token.ts`):

```typescript
import { InjectionToken } from '@angular/core';

export const RESPONSE = new InjectionToken<Response>('RESPONSE');
```
### 3. 301 Redirect Component
In a component that handles redirects, use the Response object for server-side 301 redirection. If you're not on the server, navigate client-side with Angular Router.

``` typescript 
import { CommonModule, isPlatformServer } from '@angular/common';
import { ChangeDetectionStrategy, Component, Inject, Optional, PLATFORM_ID, inject } from '@angular/core';
import { Router } from '@angular/router';

import { RESPONSE } from './tokens/server.token';

@Component({
    selector: 'app-redirect',
    standalone: true,
    imports: [CommonModule],
    templateUrl: './redirect.component.html',
    styleUrls: ['./redirect.component.scss'],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class RedirectComponent {
    private readonly platformId = inject(PLATFORM_ID);

    constructor(
        private readonly router: Router,
        @Optional() @Inject(RESPONSE) private response: Response
    ) {
        const redirectUrl = '/new-url'; // Build your URL with your own logic

        if (isPlatformServer(this.platformId) && this.response) {
            this.response.redirect(301, redirectUrl);
            this.response.end();
        } else {
            this.router.navigate([redirectUrl]);
        }
    }
}
```

### 4. Update server.ts
Update server.ts to include the Response token and ensure proper error handling:
```typescript

// All regular routes use the Angular engine
server.get('*', (req, res, next) => {
    commonEngine
        .render({
            bootstrap: AppServerModule,
            documentFilePath: indexHtml,
            url: `${req.protocol}://${req.headers.host}${req.originalUrl}`,
            publicPath: browserDistFolder,
            // Make sure to provide the RESPONSE here
            providers: [
                { provide: RESPONSE, useValue: res }
            ],
            inlineCriticalCss: true
        })
        // If the res.headersSent is true do not send again
        .then(html => (!res.headersSent ? res.send(html) : undefined))
        .catch((error: Error) => next(error));
});

```

By following these steps, your Angular app will be able to handle custom 404 and 301 responses accurately, improving both user experience and SEO.

## Trouble Shooting

- **I you receive the error: `Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client`.**
Make sure you have the following line in your `server.ts` file.
``` typescript
.then(html => (!res.headersSent ? res.send(html) : undefined))
```

- **If you get the error: `TypeError: Cannot read properties of null (reading 'status')`.**
It looks like you are running your app in development mode and forgot to mark the `this.response` as optional. Make sure you add the `?`. The `RESPONSE` is only injected on a production build.

- **If you receive the error: `Error [NullInjectorError]: R3InjectorError(Standalone[_a])[InjectionToken RESPONSE -> InjectionToken RESPONSE -> InjectionToken RESPONSE -> InjectionToken RESPONSE]`**
It seems you forgot to mark the RESPONSE injection as Optional. In your constructur check you added the @Optional() before the @Inject(RESPONSE).

## Final Thoughts
Now your Angular app is capable of returning 404 and 301 HTTP responses! Server-side rendering helps provide better responses and search engine optimization. Stay tuned for the next blog post in this series, where we dive deeper into enhancing SSR performance and improving SEO.


**What other topics are you interested in related to Angular an SEO? Let me know, I am always on the search for new topics to write about.**

_Hey there, dear readers! Just a quick heads-up: we're code whisperers, not Shakespearean poets, so we've enlisted the help of a snazzy AI buddy to jazz up our written word a bit. Don't fret, the information is top-notch, but if any phrases seem to twinkle with literary brilliance, credit our bot. Remember, behind every great blog post is a sleep-deprived developer and their trusty AI sidekick._