From e2356954ef2fd76313cdbc3d0fb3c8f572f75f06 Mon Sep 17 00:00:00 2001 From: robot Date: Thu, 6 Aug 2026 21:29:30 +0000 Subject: [PATCH] =?UTF-8?q?feat(seo):=20Phase=203=20=E2=80=94=20preload=20?= =?UTF-8?q?LCP=20image=20and=20reduce=20APP=5FINITIALIZER=20blocking?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add for hero background image (LCP element) in index.html - Remove blocking APP_INITIALIZER for config.json and station config loading - Config loads now fire asynchronously in App.ngOnInit() (browser only) - SSR/prerender renders instantly with default config — no network wait - Improves LCP, Time to First Byte, and initial paint for SEO --- src/app/app.config.ts | 17 ++-------- src/app/app.ts | 9 ++++++ src/app/services/app-config.service.ts | 36 ++++++++++++---------- src/app/services/station-config.service.ts | 5 ++- src/index.html | 4 +++ 5 files changed, 39 insertions(+), 32 deletions(-) diff --git a/src/app/app.config.ts b/src/app/app.config.ts index a59b476..3ddb1fd 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -1,31 +1,18 @@ -import { APP_INITIALIZER, ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core'; +import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core'; import { provideHttpClient, withInterceptors } from '@angular/common/http'; import { provideRouter } from '@angular/router'; import { routes } from './app.routes'; import { authInterceptor } from './interceptors/auth.interceptor'; import { - appConfigInitProvider, appConfigProvider, } from './services/app-config.service'; -import { StationConfigService } from './services/station-config.service'; - -export const stationConfigInitProvider = { - provide: APP_INITIALIZER, - useFactory: (stationConfigService: StationConfigService) => { - return () => stationConfigService.load(); - }, - deps: [StationConfigService], - multi: true, -}; export const appConfig: ApplicationConfig = { providers: [ provideBrowserGlobalErrorListeners(), - provideRouter(routes), provideHttpClient(withInterceptors([authInterceptor])), + provideRouter(routes), appConfigProvider, - appConfigInitProvider, - stationConfigInitProvider, ], }; diff --git a/src/app/app.ts b/src/app/app.ts index 6eea0d3..03a215e 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -1,5 +1,6 @@ import { Component, computed, effect, inject, OnInit, OnDestroy, PLATFORM_ID } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; +import { HttpClient } from '@angular/common/http'; import { RouterOutlet } from '@angular/router'; import { NavbarComponent } from './navbar/navbar.component'; import { MediaPlayerComponent } from './media-player/media-player.component'; @@ -7,6 +8,7 @@ import { FooterComponent } from './footer/footer.component'; import { StructuredDataComponent } from './structured-data/structured-data.component'; import { StationConfigService } from './services/station-config.service'; import { SeoService } from './services/seo.service'; +import { loadAppConfigAsync } from './services/app-config.service'; @Component({ selector: 'app-root', @@ -19,6 +21,7 @@ export class App implements OnInit, OnDestroy { private stationConfig = inject(StationConfigService); private seoService = inject(SeoService); private platformId = inject(PLATFORM_ID); + private http = inject(HttpClient); readonly hasStream = computed(() => { const cfg = this.stationConfig.config(); @@ -54,6 +57,12 @@ export class App implements OnInit, OnDestroy { // These DOM operations only run in the browser. if (!isPlatformBrowser(this.platformId)) return; + // Load runtime config.json and station config asynchronously in the + // background — do not block rendering. The app already renders with + // defaults from the SSR/prerendered HTML. + loadAppConfigAsync(this.http).catch(() => {}); + this.stationConfig.load().catch(() => {}); + // Start watching router for automatic SEO metadata updates per route. this.seoService.watchRouter(); // Handle the initial route (NavigationEnd already fired before ngOnInit). diff --git a/src/app/services/app-config.service.ts b/src/app/services/app-config.service.ts index 86c57a8..4ca469a 100644 --- a/src/app/services/app-config.service.ts +++ b/src/app/services/app-config.service.ts @@ -1,5 +1,5 @@ import { HttpClient } from '@angular/common/http'; -import { APP_INITIALIZER, InjectionToken } from '@angular/core'; +import { InjectionToken } from '@angular/core'; import { lastValueFrom } from 'rxjs'; import { environment } from '../../environments/environment'; @@ -16,15 +16,19 @@ const _appConfig: AppConfig = { apiBaseUrl: environment.apiBaseUrl, }; -export function appConfigFactory(http: HttpClient) { - return (): Promise => { - return lastValueFrom(http.get('config.json')).then( - (config) => { Object.assign(_appConfig, config); }, - () => { - // Keep defaults if config.json is not available (dev mode). - }, - ); - }; +/** + * Load runtime config.json asynchronously AFTER the app boots. + * Replaces the former APP_INITIALIZER that blocked the initial render. + * This way the page renders immediately with environment defaults, then + * patches in the runtime API URL once it arrives. + */ +export function loadAppConfigAsync(http: HttpClient): Promise { + return lastValueFrom(http.get('config.json')).then( + (config) => { Object.assign(_appConfig, config); }, + () => { + // Keep defaults if config.json is not available (dev mode). + }, + ); } export const appConfigProvider = { @@ -32,12 +36,12 @@ export const appConfigProvider = { useValue: _appConfig, }; -export const appConfigInitProvider = { - provide: APP_INITIALIZER, - useFactory: appConfigFactory, - deps: [HttpClient], - multi: true, -}; +/** + * @deprecated The APP_INITIALIZER is no longer registered by default. + * The `loadAppConfigAsync` function is called directly from `app.config.ts` + * in a non-blocking way. This export is kept for backward compatibility only. + */ +export const appConfigInitProvider = null; export function getAppConfig(): AppConfig { return _appConfig; diff --git a/src/app/services/station-config.service.ts b/src/app/services/station-config.service.ts index 983e1ff..d3c900f 100644 --- a/src/app/services/station-config.service.ts +++ b/src/app/services/station-config.service.ts @@ -6,7 +6,10 @@ import { StationConfig } from '../interfaces/station-config'; import { getAppConfig } from './app-config.service'; import { ThemeService } from './theme.service'; -/** Hardcoded defaults — used when the API is unavailable (dev fallback). */ +/** + * Hardcoded defaults — used when the API is unavailable (dev fallback). + * Rendered immediately on the initial paint so the page is never blank. + */ const DEFAULT_CONFIG: StationConfig = { id: 0, key: 'default', diff --git a/src/index.html b/src/index.html index 9221940..c8290f0 100644 --- a/src/index.html +++ b/src/index.html @@ -6,6 +6,10 @@ + + -- 2.54.0