Skip to content

feat: Server-side rendering support for demo apps#48

Open
PieterjanDeClippel wants to merge 14 commits into
masterfrom
feat/ssr-support
Open

feat: Server-side rendering support for demo apps#48
PieterjanDeClippel wants to merge 14 commits into
masterfrom
feat/ssr-support

Conversation

@PieterjanDeClippel

Copy link
Copy Markdown
Member

Summary

Closes #38

  • Add SSR support to all 3 demo apps (DemoApp, HR, Fleet) using MintPlayer.AspNetCore.SpaServices.Routing prerendering
  • Uses aspnet-prerendering Node.js bridge with @angular/platform-server for Angular server rendering
  • SpaPrerenderingService.OnSupplyData hook ready for loading PersistentObjects from DB during SSR

Changes

Root (npm):

  • Added @angular/platform-server, reflect-metadata, @angular-devkit/build-angular, aspnet-prerendering

Per-app .NET:

  • NuGet: MintPlayer.AspNetCore.SpaServices.Routing, MintPlayer.AspNetCore.Hsts
  • Program.cs: AddSpaPrerenderingService, UseImprovedHsts, conditional UseSpaStaticFilesImproved, UseSpaPrerendering
  • New Services/SpaPrerenderingService.cs with OnSupplyData hook

Per-app Angular:

  • angular.json: Added server architect target (@angular-devkit/build-angular:server)
  • tsconfig.server.json: ESNext module + bundler resolution (supports ng-bootstrap subpath exports)
  • main.server.ts: Server entry with createServerRenderer, BootstrapContext (Angular 21)
  • Split app.config.ts into shared/browser/server configs
  • DATA_FROM_SERVER injection token for server-supplied data
  • build:ssr npm script

Test plan

  • npm run build:ssr succeeds in each ClientApp directory
  • Run each demo app and verify SSR renders initial HTML on the server
  • Verify client-side hydration works after initial server render
  • Verify SPA navigation still works after hydration

🤖 Generated with Claude Code

PieterjanDeClippel and others added 14 commits March 5, 2026 20:22
Add SSR via MintPlayer.AspNetCore.SpaServices.Routing prerendering with
aspnet-prerendering Node.js bridge and @angular/platform-server.

Per-app changes:
- .NET: AddSpaPrerenderingService, UseImprovedHsts, UseSpaPrerendering,
  SpaPrerenderingService with OnSupplyData hook
- Angular: server build target, main.server.ts with BootstrapContext,
  split app.config into shared/browser/server configs, DATA_FROM_SERVER token

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- BuildRoutes registers home, po/{type} (list), po/{type}/{id} (detail)
- OnSupplyData loads PersistentObject from DB for detail route
- SparkPoDetailComponent: uses SPARK_SERVER_DATA on server, skips API calls
- SparkQueryListComponent: skips data loading on server
- Guard window.history.back() with !isPlatformServer
- Add SPARK_SERVER_DATA token to ng-spark library
- Provide SPARK_SERVER_DATA in each app's main.server.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The paramMap subscription doesn't emit during SSR, so the server data
was never applied. Move isPlatformServer check to ngOnInit which runs
synchronously before the server render completes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The template requires both item() AND entityType() to render attributes.
Previously only persistentObject was supplied, so the @if (entityType())
block was skipped and no attributes appeared in the page source.

Now OnSupplyData passes entityTypes and entityType alongside the
persistentObject, and ngOnInit sets all three signals on the server.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Supply programUnits in OnSupplyData for all routes so the shell sidebar
renders server-side. Shell components read from SPARK_SERVER_DATA on the
server and skip API calls. Also convert SpaPrerenderingService classes
to use [Inject] attribute with partial classes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Supply query, entityType, entityTypes, and queryItems in OnSupplyData
for the po-list route. SparkQueryListComponent reads from
SPARK_SERVER_DATA on the server so the table renders in the page source.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…helper

The query-list page (/query/companies) wasn't matched by BuildRoutes
because only po/{type} was registered. Add query/{queryId} route and
extract SupplyQueryListData helper used by both query-list and po-list
cases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Most query JSON files lack the entityType property, and the source
property name (e.g. "Companies") doesn't match the entity type name
(e.g. "Company") — especially for irregular plurals like People/Person.
Use SparkContext reflection (same approach as GetProgramUnits endpoint)
to map property names to entity types via their generic type argument.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two issues caused TranslatedString to render as [Object object]:

1. aspnet-prerendering uses Newtonsoft.Json which doesn't know about
   the System.Text.Json TranslatedStringJsonConverter, so it serialized
   as {"translations":{"en":"value"}} instead of {"en":"value"}.
   Fix: Add TranslatedStringNewtonsoftJsonConverter and register it
   via JsonConvert.DefaultSettings in AddSpark().

2. The currentLanguage signal wasn't set during SSR.
   Fix: Pass data["language"] resolved from Accept-Language headers
   via IRequestCultureResolver (falls back to culture.json default),
   and set currentLanguage signal in main.server.ts before rendering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…l links

canRead/canCreate/canDelete/canEdit defaulted to false in ng-spark components,
so permission-gated elements (links, buttons) were missing from SSR HTML.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Register edit route in SpaPrerenderingService, hydrate entity type and
persistent object from server data in spark-po-edit, and guard
spark-po-form API calls from running during SSR.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nce)

Supply LookupReference and Reference option data from the server during
SSR so that <select> elements render with all their options when JS is
disabled.

- Add SupplyFormOptionsAsync to all 3 demo SpaPrerenderingService files
- Inject ILookupReferenceService, load lookup values for editable attrs
- Execute queries for Reference attributes to populate dropdown options
- Pass server data through po-edit -> po-form via new inputs
- Add case-insensitive key remapping in po-form ngOnInit to handle
  camelCase dictionary keys from JSON serialization
- Add [attr.selected] on <option> elements for correct SSR pre-selection
- Add PRD document for the feature

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Server-side rendering

1 participant