Skip to content
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

prerender.io compatibility with hydration #55240

Closed
Dok11 opened this issue Apr 7, 2024 · 10 comments
Closed

prerender.io compatibility with hydration #55240

Dok11 opened this issue Apr 7, 2024 · 10 comments
Labels
area: server Issues related to server-side rendering core: hydration needs: clarification This issue needs additional clarification from the reporter before the team can investigate.
Milestone

Comments

@Dok11
Copy link
Contributor

Dok11 commented Apr 7, 2024

Which @angular/* package(s) are relevant/related to the feature request?

core

Description

As I found, this function required to use, if I want to use hydration with prerender.io service (without Angular Universal).
In my experiments all works fine, when I use next code after page is stable:

ɵannotateForHydration(this.applicationRef, this.document);

const script = this.renderer2.createElement('ng-pre-script');
script.id = 'ng-state';
script.setAttribute('type', 'application/json');
script.style = 'display: none;';
script.textContent = this.transferState.toJson();

this.renderer2.appendChild(this.document.body, script);

Some points:
Here I use ɵannotateForHydration instead of annotateForHydration, because this is in the private Angular's API.
I create ng-pre-script instead of script, because prerender.io cuts all scripts. But I can replace tag name while retrivieng html-code from backend, so it's not a trouble.

Proposed solution

Just put the annotateForHydration function into public API

Alternatives considered

Using a ɵannotateForHydration?

@Dok11 Dok11 changed the title Is it possible to make annotateForHydration function into public API? Is it possible to include annotateForHydration function into public API? Apr 7, 2024
@alxhub alxhub added feature Issue that requests a new feature area: server Issues related to server-side rendering labels Apr 8, 2024
@ngbot ngbot bot modified the milestone: Backlog Apr 8, 2024
@AndrewKushnir
Copy link
Contributor

AndrewKushnir commented Apr 9, 2024

@Dok11 the ɵannotateForHydration was not designed as a public API and as you mentioned, there is a need to include transfer state info into the page. The ɵannotateForHydration function is invoked as a part of the renderApplication function, which is exposed as a public API. Could you please let us know if using renderApplication would work for your use-case?

@AndrewKushnir AndrewKushnir added needs: clarification This issue needs additional clarification from the reporter before the team can investigate. and removed feature Issue that requests a new feature labels Apr 9, 2024
@ngbot ngbot bot modified the milestones: Backlog, needsTriage Apr 9, 2024
@Dok11
Copy link
Contributor Author

Dok11 commented Apr 9, 2024

Ok, I installed @angular/platform-server package which provided the renderApplication function. And I changed my code to this:

const promiseApp = new Promise<ApplicationRef>((resolve) => {
  resolve(this.applicationRef);
});

renderApplication(() => promiseApp, {
  document: this.document,
}).then(() => {
  const script = this.renderer2.createElement('ng-pre-script');
  script.id = 'ng-state';
  script.setAttribute('type', 'application/json');
  script.style = 'display: none;';
  script.textContent = this.transferState.toJson();

  this.renderer2.appendChild(this.document.body, script);
});

but it provides an error:

ERROR Error: NG0400: A platform with a different configuration has been created. Please destroy it first.
    at assertPlatform (core.mjs:32413:15)
    at core.mjs:32384:16
    at createServerPlatform (platform-server.mjs:16587:12)
    at platform-server.mjs:16724:25
    at Generator.next (<anonymous>)
    at asyncGeneratorStep (asyncToGenerator.js:3:1)
    at _next (asyncToGenerator.js:22:1)
    at asyncToGenerator.js:27:1
    at new ZoneAwarePromise (zone.js:1425:21)
    at asyncToGenerator.js:19:1

I think there is a conflict between regular app initialization in the main.ts:

bootstrapApplication(AppComponent, appConfig)
  .catch((err) => console.error(err));

and with manually call renderApplication(), which calls later when all components of page are loaded and rendered.

Even if we somehow fix this error in runtime, there is another trouble. The main.js of bundle with my initial approach are take 340 KB, but after installation platform-server and inculding renderApplication, the main.js of bundle takes 579 KB. So I think it will be much better with just providing annotateForHydration in the public API =)

@alxhub alxhub changed the title Is it possible to include annotateForHydration function into public API? prerender.io compatibility with hydration Apr 9, 2024
@alan-agius4
Copy link
Contributor

It appears that you're utilizing the platform-server package within the client-side version of the application, which is not officially supported.

Given that prerender.io operates by generating a static site generation page from the client-side version of the application, functionalities such as hydration, HTTP transfer cache, etc., do not function as expected. While you could currently utilize ɵannotateForHydration to address workaround some of these issue, it's unlikely to remain as effective in the future, especially as hydration evolves to support event replay and partial hydration.

If you aim to fully leverage the capabilities of Angular, the recommended approach is to utilize the Angular CLI to generate prerendered pages.

@Dok11
Copy link
Contributor Author

Dok11 commented Apr 10, 2024

functionalities such as hydration, HTTP transfer cache, etc., do not function as expected.

What exactly?
As I can see, TransferState, http get requests, layout, all of them are works fine with hydratation, when HTML are received from prerender.io, which takes these parts from ɵannotateForHydration and transferState.toJson()

@alan-agius4
Copy link
Contributor

alan-agius4 commented Apr 10, 2024

The HTTP transfer cache, which enhances performance during hydration by re-using HTTP requests performed on the server. In this case this feature will not work when using this method because HTTP requests are solely cached when Angular operates within a server context. Consequently, requests are stored on the server and reused exclusively when the application is running in the browser.

For more information, please refer to: Caching Data When Using HttpClient

From my understanding Prerender.io works pretty similar to puppeteer where the application is rendered in a browser. In my view, for hydration to be effective, the @angular/platform-server package needs to be used.

@Dok11
Copy link
Contributor Author

Dok11 commented Apr 10, 2024

because HTTP requests are solely cached when Angular operates within a server context

In my case, the <script id="ng-state" contains all get-requests which was called before I save state. Here I see URLs and responses body. After loading an app these reqests are not completes again, looks like an angular knows that they are included already.
In summary, in my case all works fine except that the annotateForHydration hidden in the private API.

@AndrewKushnir
Copy link
Contributor

In my case, the <script id="ng-state" contains all get-requests which was called before I save state. Here I see URLs and responses body. After loading an app these reqests are not completes again, looks like an angular knows that they are included already.

@Dok11 I believe this behavior would change as a result of 11705f5 - HTTP requests will no longer be added to the TransferCache on the client (they will only be added to the cache on the server).

In summary, in my case all works fine except that the annotateForHydration hidden in the private API.

The annotateForHydration was not designed as a standalone function, it's a part of the server logic to produce an HTML that Angular can hydrate later on the client. As a result, we would not be able to expose this function in the public API as is.

From my understanding Prerender.io work pretty similar to puppeteer where the application is rendered in a browser.

If Prerender.io works as Alan mentioned above, the best option would be to enable SSR or SSG for your application. In this case, Angular would produce an HTML, which can be used by Prerender.io and this HTML would be hydratable on the client.

Another option might be to avoid using hydration completely and use client-only rendering (i.e. do not use provideClientHydration() call). In this case, all content inside of an app root element would be removed on the client and Angular would re-create an application from scratch. It may result in some flickering, but the application would be functional after the bootstrap.

@Dok11
Copy link
Contributor Author

Dok11 commented Apr 15, 2024

So you have plans by changing API to work hydration only with universal and no other ways?

@alan-agius4
Copy link
Contributor

@Dok11, hydration requires usage of @angular/platform-server, ie: your application needs to be rendered on the server using the Angular rendering methods, the fact that it currently works on your client side rendered application is a lucky, not supported way of performing hydration.

@alan-agius4 alan-agius4 closed this as not planned Won't fix, can't repro, duplicate, stale Apr 15, 2024
@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 May 16, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: server Issues related to server-side rendering core: hydration needs: clarification This issue needs additional clarification from the reporter before the team can investigate.
Projects
None yet
Development

No branches or pull requests

4 participants