withastro / roadmap

Ideas, suggestions, and formal RFC proposals for the Astro project.
292 stars 29 forks source link

Enhance hybrid rendering to prerender all pages by default #539

Closed Princesseuh closed 1 year ago

Princesseuh commented 1 year ago

Body

Summary

Make an enhancement to Astro's Hybrid Rendering support to make pre-rendering the default for all pages, and opt-out instead of opt-in.

Background & Motivation

Currently, pre-rendering pages in SSR is possible thanks to Hybrid Rendering. However, this uses output: 'server' which defaults all pages to server-rendered. This can be annoying for mostly-static sites that then need to add export const prerender = true; to most pages.

One common use-case (mentioned in the original pre-rendering RFC) is a fully static site with a few server-side api routes. The current implementation requires a migrating user to add output: 'server' to their configuration and then manually add export const prerender = true to each .astro page in their src/pages directory before continuing.

This was originally discussed in the pre-rendering RFC. However it was ultimately removed because it was assumed that this would require a new third internal implementation (static and server (SSR) already exist today) which was considered too much of a maintenance burden to take on.

Goals

Non-Goals

zadeviggers commented 1 year ago

I'm excited to see progress on this!

Since creating this original proposal, I've realised that support from the editor tooling will be needed to make this feature more accessible to users. An indicator will be needed to communicate to the user what output mode is set for a particular page - perhaps an icon in the corner or a message in the status bar at the bottom. This will be even more important if the output: 'hybrid' (see below) route is chosen, but both options would benefit from this indicator.

There was also some discussion around adding an output: 'hybrid' mode that enables this behaviour to avoid a breaking(?) change to build outputs. My thinking is that it would be much more intuitive for users to set the default output mode ('static' or 'server') in the config and then change it per page by setting the prerender export; Instead, the output: 'hybrid' option has inconsistent behaviour between the different output modes, which would likely confuse users (I can choose the output mode per page with 'server' and 'hybrid', but I can't with 'static'??)

Either way, I'm excited to see this idea returned to the spotlight and hope to see it shipping shortly!

FredKSchott commented 1 year ago

Updated OP to remove any references to any specific implementation (leaving that for the RFC) and also to better reflect the current thinking around goals/non-goals for this feature based on some convos that Nate and I have had.

FredKSchott commented 1 year ago

As for actual implementation ideas: the idea that Nate and I were leaning towards IIRC (alluded to by @zadeviggers above) was the idea of a new output: 'hybrid' mode which would behave exactly the same as output: 'server', but with all pages defaulting to prerender = true instead of prerender = false.

The reasoning for the name is that this would be seen as a hybrid approach mixing qualities from all-static SSG (ouput: static) and dynamic-first SSR (output: server). Implementation-wise, this would be a trivial change since it acts exactly like output: server except with this one important prerender default changed.

There was also some discussion around adding an output: 'hybrid' mode that enables this behaviour to avoid a breaking(?) change to build outputs. My thinking is that it would be much more intuitive for users to set the default output mode ('static' or 'server') in the config and then change it per page by setting the prerender export; Instead, the output: 'hybrid' option has inconsistent behaviour between the different output modes, which would likely confuse users (I can choose the output mode per page with 'server' and 'hybrid', but I can't with 'static'??)

This idea always comes up because it sounds great on the surface ("isn't output: static basically telling Astro I want prerender = true already?") but it falls apart in practice. Unfortunately, prerender support is a feature that only exists in SSR/output: server. The reason is that when you're building a static site, there is no such thing as just adding 1-2 server-generated routes. The second you add a single server-generated route, you're shipping a server to production and leaving the deploy-simplicity of SSG static sites.

This was the reason that we ultimately shipped hybrid rendering as a feature of output: server. I can't think of a way to ship it otherwise, and I can't think of a way to change that default behavior for output: server away from defaulting to prerender = false. This is why output: hybrid is a compelling solution.

The other solution proposed was to turn output config from a string into an object, and ship something like this:

output: {
  mode: 'server' | 'static',
  prerender: true | false,
}

cc @hippotastic would love your help on this one! I still believe that either proposal here would be relatively straight-forward to implement!

zadeviggers commented 1 year ago

Thanks for the explainer @FredKSchott!

As soon as I laid eyes upon the second option, I fell in love - it's just so right. It's explicit and self-explaining (unlike the first option, which needs much explanation). This second option will also benefit from more customisation and flexibility down the line if other output features are added (the first option would pose some challenges around that).

The one thing I might change here would be to rename prerender to prerenderDefault (or something like that) to make it even more straightforward what the option does - with the current naming, it could be confused as acting more like mode, instead of just setting a default value. I'm not married to this one; it's just an idea I had.

FredKSchott commented 1 year ago

I'd be okay shipping either one! FWIW I personally prefer the first one because I'd like to optimize for the easiest migration story of static -> server (prerendered by default). Adding output: "hybrid" is a bit easier to explain to a user who just wants to add their first server route vs. asking them to understand what {mode: 'server', prerenderDefault: true} means.

Another option is to support both, where output: 'static' | 'hybrid' | 'server' all map as shortcuts to a certain {mode, prerenderDefault} config. I usually try to avoid shipping two ways to do something, but given the goals you could make an argument for this here.

zadeviggers commented 1 year ago

The idea that resonated the most with me is this:

type Config = {
ouput:
    | "static"
    | "server"
    | ({ mode: "static" } | { mode: "server"; prerenderDefault: boolean });
}

It's similar to what you suggested with string values mapping to defaults for the object configuration, but without adding the extra 'hybrid' value. My reasoning is that users should be encouraged to have a more explicit config file rather than a cleaner but more confusing configuration.

This is a good option because it will encourage users to slowly migrate their projects to the object config option without introducing a breaking change.

All that being said, your point about users wanting to add their first server route without too much hassle is very valid, so I'm still not sure what the best choice would be.

Tc-001 commented 1 year ago

It may be worth looking at https://github.com/withastro/roadmap/discussions/116 , if opt-in for SSR is being discussed, as it would be more granular than the current and proposed full-page toggle.

---
import CurrentTime from "./foo.astro"
import SlowComponent from "./bar.astro"
import SlowToBuildSvelte from "./baz.svelte"
import SmallComponent from "./bax.svalte"
---

<body>
    <CurrentTime server:dynamic /> <!-- Will render on request -->
    <SlowComponent /> <!-- Statically redered -->
    <SlowToBuildSvelte client:load /> <!-- Statically redered but still reactive -->
    <SmallComponent client:load server:dynamic /> <!-- Rendered on request and reactive -->
</body>

But it would be a maintenance burden and almost definitely a new render mode though, so may not be worth it.

matthewp commented 1 year ago

@FredKSchott

This idea always comes up because it sounds great on the surface ("isn't output: static basically telling Astro I want prerender = true already?") but it falls apart in practice. Unfortunately, prerender support is a feature that only exists in SSR/output: server. The reason is that when you're building a static site, there is no such thing as just adding 1-2 server-generated routes. The second you add a single server-generated route, you're shipping a server to production and leaving the deploy-simplicity of SSG static sites.

Can you explain this a bit more? I'm not sure I completely follow. Is this more about how Astro's build is implemented or is there a conceptual difference that you're seeing?

matthewp commented 1 year ago

Here's some of the differences between server and static (aside from the obvious of course):

  1. In static mode we don't include any query params in the Astro.url to prevent people from depending on that in dev mode. Same for a few other features, Astro.clientAddress is one that comes to mind.
  2. In server mode you are required to provide an adapter.
  3. In server mode we output to the dist/server/ and dist/client/ where as in static mode everything goes in dist/.
  4. In server mode we have an extra Vite plugin that creates the SSR entrypoint. This is a implementation difference, not an API one.

There are probably other things I can't think of right now.

matthewp commented 1 year ago

Closing as this is completed and in stable.