-
Notifications
You must be signed in to change notification settings - Fork 87
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2e008d4
commit 191d8c9
Showing
3 changed files
with
65 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# Initial vs. Subsequent Loads | ||
|
||
In a Persues app, there are two ways for a page in your app to be loaded, and it's important to understand this if you want to work with the more advanced features of Perseus. The first way is an *initial load*, which is when a user comes onto your site from an external URL (e.g. a search engine). The second is a *subsequent load*, which is when a user moves from one page on your site to another (e.g. a link on your landing page takes them to an about page). | ||
|
||
## Initial Loads | ||
|
||
The main thing to understand about initial loads is that they have to send the user *everything*: HTML, the Wasm bundle, etc. After this is all in the user's browser, Perseus can take over the routing process to optimize for performance by only fetching what it needs. First, we need to get everything into the user's browser though. | ||
|
||
At the level of HTTP, an initial load request looks as you might expect. Requesting a page at `/about` requests `/about`, and the server compiles a full HTML file with everything the user needs, sending it to them. For exported apps, these files are compiled when you build your app (which is why you can't use request-time state in exporting, there's no way to update the state in the precompiled HTML before it goes to the client). | ||
|
||
This HTML file has in it a prerendered version of the page, meaning the user will see content straight away, even though the Wasm bundle might take a moment longer to load, after which time the page will become reactive, and you can click buttons, etc. | ||
|
||
Once this Wasm is loaded, all other links in the app are controlled by subsequent loads. Importantly, if the user goes to an external URL and then comes back, another initial load will occur (though hopefully their browser will have cached the Wasm bundle, reducing the load time to almost zero). | ||
|
||
One caveat to all this is if i18n is being used, in which case there's unfortunately no way for the server to reliably know which language a page should be returned in. If the user requested `/en-US/about`, no problem, but if they just gave us `/about`, we need to send them a script to figure out their locale. Specifically, this comes in a blank HTML page that includes the Wasm bundle, which will then detect the locale and mvoe ahead with rendering. | ||
|
||
Unfortunately, this approach does lead to a moment of having a blank screen before the Wasm bundle has loaded, something that we aim to resolve in the longer-term. | ||
|
||
## Subsequent Loads | ||
|
||
Once the user's browser has the Wasm bundle, every time they go to a new page, we don't need to fetch that bundle again, or a whole lot actually. We don't even need the HTML scaffold --- just the page's HTML content, its `<head>`, and its state. While you may see a transition from, say, `/` to `/about`, in reality that's just superficial, and no request to `/about` has been made. In fact, a request to somewhere in `/.perseus/` has been made, which will return a JSON object with exactly what we need, minimizing load times between pages, and meaning your browser has to do no more work. From its perspective, we haven't actually moved to a new page. | ||
|
||
This is the approach of *single-page apps*, which aren't really just one page, but they use a routing approach like this for performance. Unfortunately, SPAs have a whole host of other problems caused by this routing, all of which Perseus ims to solve. If you find any problems with our subsequent loads system, please [open an issue](https://github.com/arctic-hen7/perseus/issues/new/choose)! | ||
|
||
*Note: currently, scroll positions are not preserved by the subsequent load system, though this is an upstream issue in Sycamore currently being worked on.* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# Router | ||
|
||
Most of the time, you will never have to worry about how Perseus' router works. However, if you're working on a new server integration, or something else very low-level, you might encounter some thorns to do with how Perseus figures out which page to render. This might seem like a simple problem. The user requests `/about`? Render the `about` template. Now introduce [incremental generation](:reference/state-generation). Now it's a little more complex. For clarity, this page will outline the way Perseus' routing algorithm actually works internally. | ||
|
||
If you're just building apps with Perseus, you shouldn't have to read this, it's more for those working with the internals. | ||
|
||
Note that this algorithm is executed on the client-side for _subsequent loads_ and on the server-side for _initial loads_ (see [here](:reference/initial_subsequent_loads) for details on those). | ||
|
||
Also note that Perseus' routing algorithm is based on a file called `render_conf.json`, which is stored in `dist/`. Importantly, this is stored in memory by the server, and it's interpolated directly into the HTML sent to the user's browser. (Meaning apps with *very\** large numbers of pages should consider incremental generation even if their build times are fine, since it may actually improve load times by a little. Take a look at the `<script>` tags in the `<head>` of this website to see what we mean!) | ||
|
||
Here's an example render configuration (for the [state generation example](https://github.com/arctic-hen7/perseus/blob/main/examples/core/state_generation)), which maps URL to template name. | ||
|
||
```json | ||
{ | ||
"amalgamation":"amalgamation", | ||
"revalidation_and_incremental_generation/test":"revalidation_and_incremental_generation", | ||
"incremental_generation/blah/test/blah":"incremental_generation", | ||
"incremental_generation/test":"incremental_generation", | ||
"build_paths/blah/test/blah":"build_paths", | ||
"build_paths/test":"build_paths", | ||
"revalidation_and_incremental_generation/*":"revalidation_and_incremental_generation", | ||
"request_state":"request_state", | ||
"build_paths":"build_paths", | ||
"revalidation":"revalidation", | ||
"build_state":"build_state", | ||
"incremental_generation/*":"incremental_generation", | ||
"revalidation_and_incremental_generation/blah/test/blah":"revalidation_and_incremental_generation" | ||
} | ||
``` | ||
|
||
Here are the algorithm's steps (see [`match_route.rs`](https://github.com/arctic-hen7/perseus/blob/main/packages/perseus/src/router/match_route.rs)): | ||
|
||
1. If the path is empty, set it to `index` (which is used for the landing page). | ||
2. Try to directly get the template name by trying the path as a key. This would work for anything not using incremental generation (in the above example, anything other than `incremental_generation/*` and `revalidation_and_incremental_generation/*`). | ||
3. Split the path into sections by `/` and iterate through them, performing the following on each section (iterating forwards from the beginning of the path, becoming more and more specific): | ||
1. Make a path out of all segments up to the current point, adding `/*` at the end (indicative of incremental generation in the render configuration). | ||
2. Try that as a key, return if it works. | ||
3. Even if we have something, continue iterating until we have nothing. This way, we get the most specific path possible (and we can have incremental generation in incremental generation). |