Skip to content

feat(@angular/build): add prerenderFormat option to prerender routes as <route>.html - #34180

Open
JohannesHoppe wants to merge 2 commits into
angular:mainfrom
JohannesHoppe:feat/prerender-format
Open

JohannesHoppe wants to merge 2 commits into
angular:mainfrom
JohannesHoppe:feat/prerender-format

Conversation

@JohannesHoppe

@JohannesHoppe JohannesHoppe commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

PR Checklist

Please check to confirm your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • Other... Please describe:

What is the current behavior?

Prerendering always writes a route to <route>/index.html. On static hosts, this forces a choice between clean URLs and no redirects. You can't have both:

  • Clean URLs, but redirects: links use /<route>. Every direct request (search engines, bookmarks, shared links) is first redirected (301/308) to /<route>/, and the Angular router then removes the trailing slash again.
  • No redirects, but trailing slashes everywhere: links have to use /<route>/, and the app needs TrailingSlashPathLocationStrategy to keep the slash in the address bar.

Issue Number: #29173

What is the new behavior?

A new application builder option prerenderFormat makes both possible: clean URLs without a trailing slash, served directly with a status code 200.

  • "directory" (default): /foo/bar is written to foo/bar/index.html, unchanged.
  • "file": /foo/bar is written to foo/bar.html, so hosts that serve <route>.html for /<route> respond to /foo/bar without a redirect.

Mirrors Astro's build.format ('directory' | 'file'); Next.js, SvelteKit and Hugo offer the same choice via trailingSlash/uglyURLs.

  • The root route (after removing the baseHref option) stays index.html, so / and /<locale>/ keep being served by the host's directory index.
  • A route that cannot be written safely to <route>.html keeps <route>/index.html, and the build reports a warning that names the route. This applies to routes whose last segment is index in any casing (for example /index, /Index or /docs/index, whose file would be the index.html of the parent path), to routes whose file would be the index output of the application (such as index.csr.html), and to routes whose file name is already used by another route (compared case-insensitively).
  • "file" is only considered when the build does not produce a server. The @angular/ssr runtime looks up prerendered pages as <route>/index.html (AngularServerApp.buildServerAssetPathFromRequest, CommonEngine.retrieveSSGPage), and with a server there is no redirect to avoid, since the generated server.ts serves static files with redirect: false. With outputMode: "server", or ssr without outputMode, the build warns that the option is not considered and prerenders to <route>/index.html. The dev server does not prerender and shows no warning.
  • It is a top-level option rather than part of prerender: when outputMode is set, the prerender option is not considered (options.ts), and outputMode: "static" is the documented way to build a fully static application.

As suggested by @SanderElias in the issue, the option description states that not all hosting services support this. Happy to rename the option if you prefer a different name.

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

As a stopgap, I built a builder (@angular-schule/prerender-format) that wraps @angular/build:application. It only works by replacing the internal prerenderPages() export of @angular/build at runtime to rename the output files, which is fragile and can break with any internal refactoring. A built-in option is the clean solution.

The option description in schema.json feeds the ng build reference on angular.dev. If this lands, I'm happy to follow up with a short section in the SSR guide ("Generate a fully static application") in angular/angular.

…s as `<route>.html`

Prerendered routes are written to `<route>/index.html`. Static hosts serve such a file under
`/<route>/`, so a request to `/<route>` is first redirected to the URL with a trailing slash,
which the Angular router then removes again.

The new `prerenderFormat` option of the application builder accepts `directory` (default,
unchanged behavior) and `file`, like the `build.format` option of Astro. With `file`, routes
are written to `<route>.html`, which avoids the redirect on hosts that serve `<route>.html`
for `/<route>`. The root route (after removing the `baseHref` option) is still written to
`index.html`. Static redirect pages follow the same layout.

A route that cannot be written safely to `<route>.html` keeps `<route>/index.html` and the
build reports a warning: routes whose last segment is `index` in any casing, routes whose file
would be the index output of the application, and routes whose file name is already used by
another route.

`file` is only considered when the build does not produce a server (for example with
`outputMode: "static"`), because the `@angular/ssr` runtime looks up prerendered pages as
`<route>/index.html`. When the build produces a server, a warning is reported and routes are
written to `<route>/index.html`.

Closes angular#29173
@JohannesHoppe
JohannesHoppe marked this pull request as ready for review September 26, 2026 13:35

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the "prerenderFormat" option to the Angular application builder, allowing prerendered pages to be output as ".html" files (using the "file" format) instead of the default "/index.html" structure. It includes robust conflict resolution logic for routes that cannot be safely written to ".html" (such as routes named "index" or matching the application's index file), falling back to the directory format with a warning. The review feedback suggests a performance optimization in "prerender.ts" to pre-lowercase the "indexOutput" variable once outside the route rendering loop, rather than performing redundant string operations inside "getFileFormatConflict" for every route.

Comment thread packages/angular/build/src/utils/server-rendering/prerender.ts
Comment thread packages/angular/build/src/utils/server-rendering/prerender.ts
Comment thread packages/angular/build/src/utils/server-rendering/prerender.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: @angular/build detected: feature PR contains a feature commit

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant