-
Notifications
You must be signed in to change notification settings - Fork 2.4k
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
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
RFC: Static Pages / Browser Only Fetching #179
Comments
About prerendering, do you think it would be possible to support both SSR and prerendering at the same time? I'm thinking on something like preprender the most popular pages of a website (e.g. popular products, latest articles, etc.) and keep SSR for the rest, another thing is that I could store the date (or Remix could do it automatically when the prerender happen) and in the loader verify "was this page prerendered in the last week? then continue using the same data/html, is it older? then do SSR" this way you can use preprendering mostly for a few pages, this will only work if prerendering is "call the loader at build time and SSR at runtime". Another feature that could be interesting is if a route doesn't need any loader (like an about page) then prerender it automatically. If the root has a loader then this will not happen thought and it's super common to have one there so maybe doesn't deserver the effort to implement this. Also because of the nested layouts this would mean detect if not only the leaf route but any other parent route doesn't have a loader. About client loader, what would happen when the user opens a new browser tab and goes to a page with a client loader, what will the user see while the loader is running client-side? Right now, the user browser will take care of showing a spinner, but with the loader working client-side Remix would need to send an empty or almost empty page, download the component and client-loader code and then call the loader, once finish render the page. Should there be a way to export a fallback page to render server-side in those cases? export function Fallback() {
return <h1>Loading...</h1>
} Something like that, it could also be called Skeleton, but fallback makes more sense in React since Suspense uses that word. With this Remix can SSR the fallback/skeleton of that route (inside any parent layout if needed) and then after calling the client-loader switch to the actual page. Another option could be to provide a way for the page to detect if it is still pending to be rendered. export default function View() {
let isPending = useIsPendingClientLoaderOnFirstCall() // name TBD
if (isPending) return <h1>Loading...</h1>
return <ActualUI />
} |
Great ideas and questions @sergiodxa, I just pulled up some old notes and created a new issue about |
Coming back to this, I need to think this through some more, but I think maybe we The hook would work, but one thing I really hate in React is having to add loading/error/success branching in every component. And then when you need that data for other hooks you have to split the component up into separate components anyway. I like that in Remix your default component can always plan on having data. |
The Pending export is way better, I also hate the branching inside components, that’s something Suspense improved a lot by moving the fallback and error handling to the parent. Next.js uses the hook approach for ISR support so you do let { isFallback } = useRouter() To know if you have to render a fallback for a new page or not and I hated using it. Remix should be better at this and the Pending export is the way. |
For what it's worth, if you decide to go with either/or I definitely prefer the |
Re: pre-renderingThis all sounds great. I really like the idea of providing the context object up front when pre-rendering. There's a ton of flexibility there. My main concern would be what we'd need to do in our starter templates to make sure that all of our supported hosts know how to serve the HTML pages we put in the An interesting experiment would be to do an entire site like this and deploy it to an S3 bucket and see what routing rules we need to setup on the bucket to get the site to be served correctly. Idea: exports.preRender = string | string[] | fnMaybe instead of a function, exports.preRender = ["/about", "/team"]; You wouldn't get to provide Idea: pre-rendering with path "globs"
exports.preRender = ["/about", "/blog/*"];
// or, to pre-render the whole site:
exports.preRender = "*"; This would ofc be an error if you had any route paths with dynamic segments in the matched set of routes since we can't just guess them. But it could be nice if you just had a bunch of static routes in a folder somewhere. Future idea: Add S3 as option in
|
I love this API and the flexibility it'll bring to Remix. This looks clunky however:
I'd much prefer the |
If you add the Build-Time Pre-Rendering feature, please allow using runtime and build-time data fetching in the same route. Build-time: Get static data of the route, like translations, an article data, etc. If you have both it will not really pre-render the HTML at build, just fetch the data and store it in the context so the loader can use it (or not if it's too old) |
I started a discussion thread not realizing this issue was already open: I think it would be most beneficial to have a I think what I wrote and what is being proposed here is somewhat different. I think there is tremendous value in executing a loader/action on a document request (on the server) and then executing that same loader/action on the client for a transition. The client has to make an HTTP request anyways why not bypass the proxy? |
How would you use a decorator there if they are only usable with classes and their methods? Also they are not standard yet. Also there’s a huge benefit of running loaders or actions only server side, you can recover extra data, in your example the list of gists only use two properties but each gist from the GitHub API comes with a lot, you could remove them on the server and send less data to the browser, you lose this if the loader runs client side |
Ugh. that is weird. ok well maybe a
Sure, but you're making a trade-off between amount of data being transferred and cacheability of that data. GitHub can cache that resource on an edge server indefinitely and purge it whenever it changes. If I am forced to proxy the request, that cache is effectively useless since the user is forced to make a request to my origin server (or I run the entire app at the edge, or I handle the caching and purging myself, which may be impossible). Regardless, I don't think you can definitively say that less data transfer === less latency. Indeed, it may be the exact opposite depending on how far (geographically) the request has to travel in order to get a response. I think it would be more prudent to let engineers make that assessment on a per-loader/action basis. I find it rather odd that if you build a single-page application with React, everything from a 3rd party API is fetched on the client, but if you use a framework that enables SSR (Remix, Next.js, Gatsby, etc.) then all of the sudden all 3rd party API requests are forced to be made on the server (if you want SSR as well). |
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
Remix is pretty opinionated about always server rendering, but that's not really an opinion, it's just a starting point.
Remix's data loading, data mutation, and asset loading conventions around nested routes are useful whether you're server rendering, fetching data clientside only, or static rendering.
The following APIs will enable you to easily change which mode your route works with:
Build-Time Pre-Rendering
It seems the thing to do in this space is make up new acronyms. So we'll call this BTPR 😋. Just kidding, we'll just call it pre-rendering.
Inside of your remix config, you can define a
prerender
The loaders for these routes will be called:
This gives you the ability to fetch everything in
prerender
or let the loaders handle it:Or put it on context in
prerender
And now a loader can just find it's post from context
When Remix is bundling, it can render these pages and dump them into your
public/
folder.Clientside Data Fetching/Mutations
Remix calls your loaders serverside, even on script transitions. This is great for a few reasons:
That said, many databases allow you to connect to them in the browser, and some pages you don't care about SSR. Perhaps you're using clientside authentication with JWTs on pages you don't care to server render and you'd like to talk directly to your APIs from the browser w/o the Remix server in the middle (like FaunaDB, Hasura, or Firestore).
If you export a
clientLoader
, then Remix will use it instead of theloader
on script transitions.There is no
context
because the server isn't involved, but we can still provide a request that is nearly identical to what theloader
would get (just no headers, pretty much). Note that in place of cookies and sessions apps we use the browser's built-insessionStorage
.And of course you can have a
clientAction
as well.One image in our mind we keep when building Remix is the idea of "levers" you can pull to change how your app is delivered w/o having to change much (or anything) about your application code. For example, removing
<Scripts/>
and your app continues to function the same except with full page document loads.A lever here could be a
sessionStorage
abstraction with the same API as our HTTP session storage. Consider the cookie session storage:We could have the same API for browser
sessionStorage
This means an app could theoretically write the code like this:
And then switch it to this:
Of course, the
request
won't have a "Cookie" header, but the browser storage can just ignore it (and the app code should probably delete that part) but the point is this is another lever you can pull to change where your code is running without having to really change your code at all.All the other tools in Remix will work identically:
usePendingLocation
,usePendingFormSubmit
, the data diffing for loaders, etc.What about both?
There's the possibility that you could export both
loader
andclientLoader
. What do we do? Useloader
for document requests andclientLoader
for script requests?I think this opens up far too many questions and is hard to pin down a solid use-case. Considering an authenticated user to get the same result on the server and the client would require a lot of code to have them authenticated in both places. Or even just a database connection, you'd need to establish it on the client and the server.
This is much easier to think about (and no doubt implement) if it's either/or.
Alternative API
Instead of changing the name of the exported function we could add a property to the function:
Now it really is just a lever to change where the code runs, it also makes it impossible to define a
loader
andclientLoader
.What do you think?
The text was updated successfully, but these errors were encountered: