Skip to content

SSG allow loading static assets #29236

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
1 task done
AmitMY opened this issue Dec 29, 2024 · 8 comments · Fixed by #29252
Closed
1 task done

SSG allow loading static assets #29236

AmitMY opened this issue Dec 29, 2024 · 8 comments · Fixed by #29252

Comments

@AmitMY
Copy link
Contributor

AmitMY commented Dec 29, 2024

Command

build

Is this a regression?

  • Yes, this behavior used to work in the previous version

The previous version in which this bug was not present was

17

Description

I am using transloco for localization.
When building (used to be prerendering) now the build process tries to render the pages.
In doing so, it hits this loader, which loads a static asset (the language in question)

@Injectable({providedIn: 'root'})
export class HttpLoader implements TranslocoLoader {
  private http = inject(HttpClient);

  getTranslation(langPath: string): Observable<Translation> {
    return this.http.get<Translation>(`assets/i18n/${langPath}.json`);
  }
}

However, the HTTP request fails, since there seems to not be an http server involved in the SSG process.

If I run this in SSR, it also fails, but that is because it can't handle the path, and requires a full URL (http://localhost:4000/assets/...)

If I accept not having a server, I need to have a similar behavior to what I do during testing:

    if (isPlatformServer(this.platformId)) {
      const path = require('path');
      const filePath = path.join(__dirname, '..', '..', '..', assetPath);
      console.error('filePath', filePath);
      const fs = require('fs');

      const data = fs.readFileSync(filePath, 'utf8');
      return of(JSON.parse(data));
    }

But this relies on node modules, and the builder errors:

The package "fs" wasn't found on the file system but is built into node. Are you trying to bundle for node? You can use "platform: 'node'" to do that, which will remove this error.

Therefore, my issues are:

  1. SSG should have some sort of way to load static assets.
  2. SSR should not need the full URL. somehow, it should understand that any path requested is under the same host as the REQUEST.

Minimal Reproduction

  1. Make a new app, with ssr and ssg ("prerender": true, "ssr": {"entry": "src/server.ts"}`)
  2. under src/assets add something.json
  3. in the AppComponent's ngOnInit function, make this.httpClient.get('/assets/something.json').subscribe()
  4. run ng build --configuration=production

Exception or Error

- SSG should have a way to load static assets
- SSR should be able to load assets using HTTP

Your Environment

Angular CLI: 19.0.6
Node: 22.0.0
Package Manager: npm 10.5.1
OS: darwin arm64

Angular: 19.0.5
... animations, common, compiler, compiler-cli, core, forms
... platform-browser, platform-browser-dynamic, platform-server
... router, service-worker

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1900.6
@angular-devkit/build-angular   19.0.6
@angular-devkit/core            19.0.6
@angular-devkit/schematics      19.0.6
@angular/cdk                    19.0.4
@angular/cli                    19.0.6
@angular/material               19.0.4
@angular/ssr                    19.0.6
@schematics/angular             19.0.6
rxjs                            7.8.1
typescript                      5.6.3
zone.js                         0.15.0

Anything else relevant?

In the old prerendering, it used to work.

@alan-agius4
Copy link
Collaborator

Load of assets when using SSG has been supported for some time now, Although you are required to use withFetch.

If the problem persists please provide a minimal runnable reproduction of the problem.

You can read here why this is needed. A good way to make a minimal repro is to create a new app via ng new repro-app and adding the minimum possible code to show the problem. Then you can push this repository to github and link it here.

@AmitMY
Copy link
Contributor Author

AmitMY commented Dec 29, 2024

If I set up a new app:
Image
Added public/something.json file with some content, , and added provideHttpClient(withFetch()), it indeed works.

  private http = inject(HttpClient);

  ngOnInit() {
    this.http.get('something.json').pipe(
      tap((res: any) => console.log("Success", res)),
      catchError((err: any) => {
        console.log("Error", err);
        return of(null);
      })
    ).subscribe();
  }

Indeed prints "success"


However, in my app, I am using withFetch

    provideHttpClient(withFetch(), withInterceptorsFromDi()),

But still it doesn't work:

Couldn't load translation file 'assets/i18n/en.json' dt {
  headers: e {
    headers: Map(0) {},
    normalizedNames: Map(0) {},
    lazyInit: undefined,
    lazyUpdate: null
  },
  status: 0,
  statusText: 'Unknown Error',
  url: 'assets/i18n/languages/en.json',
  ok: false,
  type: undefined,
  name: 'HttpErrorResponse',
  message: 'Http failure response for assets/i18n/languages/en.json: 0 undefined',
  error: TypeError: Invalid URL

My repository is https://github.com/sign/translate
http setup: https://github.com/sign/translate/blob/98d54014850acf6d87d743e8002f022b57a3ece6/src/app/app.config.ts#L54

@AmitMY
Copy link
Contributor Author

AmitMY commented Jan 3, 2025

Hi @alan-agius4 - any ideas on this?

alan-agius4 added a commit to alan-agius4/angular-cli that referenced this issue Jan 6, 2025
…during server fetch

Ensures proper handling of relative URLs to prevent errors in server-side fetch operations.

Closes angular#29236
alan-agius4 added a commit that referenced this issue Jan 6, 2025
…during server fetch

Ensures proper handling of relative URLs to prevent errors in server-side fetch operations.

Closes #29236
alan-agius4 added a commit that referenced this issue Jan 6, 2025
…during server fetch

Ensures proper handling of relative URLs to prevent errors in server-side fetch operations.

Closes #29236

(cherry picked from commit a0b4ea2)
@AmitMY
Copy link
Contributor Author

AmitMY commented Jan 9, 2025

@alan-agius4 Unfortuantely, after updating to the latest release, it is not yet solved.

My updates:

 @angular-devkit/architect          0.1900.6    0.1900.7
 @angular-devkit/build-angular        19.0.6      19.0.7
 @angular/animations                  19.0.5      19.0.6
 @angular/cdk                         19.0.4      19.0.5
 @angular/cli                         19.0.6      19.0.7
 @angular/common                      19.0.5      19.0.6
 @angular/compiler                    19.0.5      19.0.6
 @angular/compiler-cli                19.0.5      19.0.6
 @angular/core                        19.0.5      19.0.6
 @angular/forms                       19.0.5      19.0.6
 @angular/material                    19.0.4      19.0.5
 @angular/platform-browser            19.0.5      19.0.6
 @angular/platform-browser-dynamic    19.0.5      19.0.6
 @angular/platform-server             19.0.5      19.0.6
 @angular/router                      19.0.5      19.0.6
 @angular/service-worker              19.0.5      19.0.6
 @angular/ssr                         19.0.6      19.0.7

Error:

HttpErrorResponse {
  headers: _HttpHeaders {
    headers: Map(0) {},
    normalizedNames: Map(0) {},
    lazyInit: undefined,
    lazyUpdate: null
  },
  status: 0,
  statusText: 'Unknown Error',
  url: 'assets/i18n/countries/en.json',
  ok: false,
  type: undefined,
  name: 'HttpErrorResponse',
  message: 'Http failure response for assets/i18n/countries/en.json: 0 undefined',
  error: TypeError: Failed to parse URL from assets/i18n/countries/en.json
      at node:internal/deps/undici/undici:12500:13
      at _ZoneDelegate.invoke (/Users/amitmoryossef/dev/sign/translate/node_modules/zone.js/fesm2015/zone-node.js:369:28)
      at ZoneImpl.run (/Users/amitmoryossef/dev/sign/translate/node_modules/zone.js/fesm2015/zone-node.js:111:43)
      at eval (/Users/amitmoryossef/dev/sign/translate/node_modules/zone.js/fesm2015/zone-node.js:1221:40)
      at _ZoneDelegate.invokeTask (/Users/amitmoryossef/dev/sign/translate/node_modules/zone.js/fesm2015/zone-node.js:402:33)
      at ZoneImpl.runTask (/Users/amitmoryossef/dev/sign/translate/node_modules/zone.js/fesm2015/zone-node.js:159:47)
      at drainMicroTaskQueue (/Users/amitmoryossef/dev/sign/translate/node_modules/zone.js/fesm2015/zone-node.js:581:35) {
    [cause]: TypeError: Invalid URL
        at new URL (node:internal/url:804:36)
        at new Request (node:internal/deps/undici/undici:4839:25)
        at fetch (node:internal/deps/undici/undici:9651:25)
        at fetch (node:internal/deps/undici/undici:12498:10)
        at fetch (node:internal/bootstrap/web/exposed-window-or-worker:79:16)
        at eval (/Users/amitmoryossef/dev/sign/translate/node_modules/zone.js/fesm2015/zone-node.js:1472:40)
        at proto.<computed> (/Users/amitmoryossef/dev/sign/translate/node_modules/zone.js/fesm2015/zone-node.js:913:24)
        at _FetchBackend.FetchBackend.fetchImpl (/Users/amitmoryossef/dev/sign/translate/.angular/cache/19.0.7/sign-translate/vite/deps_ssr/chunk-YUNSYC3W.js:1233:41)
        at eval (/Users/amitmoryossef/dev/sign/translate/.angular/cache/19.0.7/sign-translate/vite/deps_ssr/chunk-YUNSYC3W.js:1249:71)
        at _ZoneDelegate.invoke (/Users/amitmoryossef/dev/sign/translate/node_modules/zone.js/fesm2015/zone-node.js:369:28) {
      code: 'ERR_INVALID_URL',
      input: 'assets/i18n/countries/en.json'
    }
  }
}

@alan-agius4
Copy link
Collaborator

I tried again your reproduction and I am not getting that error, I am seeing other error but these are legit as the file does not exist

ERROR HttpErrorResponse {
  headers: _HttpHeaders {
    headers: Map(0) {},
    normalizedNames: Map(0) {},
    lazyInit: undefined,
    lazyUpdate: null
  },
  status: 0,
  statusText: 'Unknown Error',
  url: '/licenses.json',
  ok: false,
  type: undefined,
  name: 'HttpErrorResponse',
  message: 'Http failure response for /licenses.json: 0 undefined',
  error: TypeError: Failed to parse URL from /licenses.json
      at node:internal/deps/undici/undici:13178:13
      at _ZoneDelegate.invoke (.angular/prerender-root/0ededdec-950b-4f09-bb9e-894731c005fa/node_modules/zone.js/fesm2015/zone-node.js:369:28)
      at ZoneImpl.run (.angular/prerender-root/0ededdec-950b-4f09-bb9e-894731c005fa/node_modules/zone.js/fesm2015/zone-node.js:111:43)
      at <anonymous> (.angular/prerender-root/0ededdec-950b-4f09-bb9e-894731c005fa/node_modules/zone.js/fesm2015/zone-node.js:1221:40)
      at _ZoneDelegate.invokeTask (.angular/prerender-root/0ededdec-950b-4f09-bb9e-894731c005fa/node_modules/zone.js/fesm2015/zone-node.js:402:33)
      at ZoneImpl.runTask (.angular/prerender-root/0ededdec-950b-4f09-bb9e-894731c005fa/node_modules/zone.js/fesm2015/zone-node.js:159:47)
      at drainMicroTaskQueue (.angular/prerender-root/0ededdec-950b-4f09-bb9e-894731c005fa/node_modules/zone.js/fesm2015/zone-node.js:581:35) {
    [cause]: TypeError: Invalid URL
        at new URL (node:internal/url:797:36)
        at new Request (node:internal/deps/undici/undici:9269:25)
        at fetch (node:internal/deps/undici/undici:9998:25)
        at fetch (node:internal/deps/undici/undici:13176:10)
        at fetch (node:internal/bootstrap/web/exposed-window-or-worker:72:12)
        at patchedFetch (node_modules/@angular/build/src/utils/server-rendering/fetch-patch.js:40:20)
        at <anonymous> (.angular/prerender-root/0ededdec-950b-4f09-bb9e-894731c005fa/node_modules/zone.js/fesm2015/zone-node.js:1472:40)
        at proto.<computed> (.angular/prerender-root/0ededdec-950b-4f09-bb9e-894731c005fa/node_modules/zone.js/fesm2015/zone-node.js:913:24)
        at _FetchBackend.FetchBackend.fetchImpl (.angular/prerender-root/0ededdec-950b-4f09-bb9e-894731c005fa/node_modules/@angular/common/fesm2022/http.mjs:1584:41)
        at <anonymous> (.angular/prerender-root/0ededdec-950b-4f09-bb9e-894731c005fa/node_modules/@angular/common/fesm2022/http.mjs:1602:69) {
      code: 'ERR_INVALID_URL',
      input: '/licenses.json'
    }
  }
}

@AmitMY
Copy link
Contributor Author

AmitMY commented Jan 9, 2025

Interesting! You are correct, about that file.

ng build problem solved 🥳 (only licenses.json file missing)
ng serve now has the original problem 😢 (all files, including assets missing)

Much less critical than before, thank you!

@alan-agius4
Copy link
Collaborator

alan-agius4 commented Jan 9, 2025

That is caused because provideServerRendering() is not invoked. See: https://github.com/sign/translate/blob/5d15b7910bd2d540d8b03e0691fc7959378c7807/src/app/app.config.server.ts#L6 these API will also fail on production servers.

I am actually surprised that you got this far without it.

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Feb 9, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
2 participants