108 Matching Annotations
  1. Sep 2023
    1. On subsequent page loads using client-side navigation, we can allow the slow data to be streamed in. Client-side navigation will only occur if JavaScript is available, so there are no downsides to using promise streaming. If the user doesn’t have JavaScript, each navigation will trigger a full page load, and we will again wait for all data to resolve.We can switch between these two behaviors using the isDataRequest property on the RequestEvent passed to the load function. When this property is false, the request is for the HTML for the initial page load, and we should wait for all data to resolve. If the property is true, then the request is from SvelteKit’s client-side router and we can stream the data in.

      js export async function load({isDataRequest}) { const slowData = getSlowData(); return { nested: { slow: isDataRequest ? slowData : await slowData } }; }

    2. We can use Promise.race to give our promise a few hundred milliseconds to resolve. Promise.race takes an array of promises, and resolves when any one of those promises resolve. We can pass our delay call and our data fetching call, and conditionally await the result depending on which one resolves first.

      In this example, we race two promises: a 200ms delay and the actual data call we want to make. If the delay resolves first, then the data call is taking longer than 200ms and we should go ahead and render the page with partial data. If the data call resolves first, then we got the data under the time limit and we can render the page with complete data.

      ```js const TIME_TO_RESOLVE_MS = 200; export async function load() { const slowData = getSlowData();

      const result = await Promise.race([delay(TIME_TO_RESOLVE_MS), slowData]);

      return { nested: { slow: result ? result : slowData } }; }

      async function getSlowData() { // randomly delay either 50ms or 1s // this simulates variable API response times await delay(Math.random() < 0.5 ? 50 : 1000); return '😴'; }

      function delay(ms) { return new Promise(res => setTimeout(res, ms)); } ```

  2. Aug 2023
    1. SvelteKit will automatically await the fetchPost call before it starts rendering the page, since it’s at the top level. However, it won’t wait for the nested fetchComments call to complete – the page will render and data.streamed.comments will be a promise that will resolve as the request completes. We can show a loading state in the corresponding +page.svelte using Svelte’s await block:

      js export const load: PageServerLoad = () => { return { post: fetchPost(), streamed: { comments: fetchComments() } }; }; ```svelte

      <script lang="ts"> import type { PageData } from './$types'; export let data: PageData; </script> <article> {data.post} </article>

      {#await data.streamed.comments} Loading... {:then value} <br />

        {#each value as comment}
      1. {comment}
      2. {/each}

      {/await} ```

  3. Jul 2023
  4. Jun 2023
  5. May 2023
  6. Apr 2023
  7. Mar 2023
    1. Streaming allows you to break down the page's HTML into smaller chunks and progressively send those chunks from the server to the client.

  8. hydrogen.shopify.dev hydrogen.shopify.dev
    1. ```js import {defer} from "@shopify/remix-oxygen";

      export async function loader({ params: {handle}, context: {storefront} }) { const {product} = storefront.query({ query: #graphql query Product( $country: CountryCode, $language: LanguageCode, $handle: String! ) @inContext(country: $country, language: $language) product(handle: $handle) { id title }, variables: {handle}, cache: storefront.CacheLong() }); const {productRecommendations} = storefront.query({ query: #graphql query ProductRecommendations( $country: CountryCode, $language: LanguageCode, $handle: String! ) @inContext(country: $country, language: $language) productRecommendations(handle: $handle) { id title } }, variables: {handle} }); if (!product) { throw new Response('Not Found', { status: 404, }); } return defer({ product: await product, productRecommendations, }); } ```

    1. The loader defers a promise that will resolve only when the json's progress has hit 100.

      js export async function loader({ params }: LoaderArgs) { if (!params.hash) return redirect("/") const pathname = path.join( "public", "items", `${params.hash}.json`, ) const file = fs.readFileSync(pathname) if (!file) return redirect("/") const item = JSON.parse(file.toString()) if (!item) return redirect("/") if (item.progress === 100) { return defer({ promise: item, }) } return defer({ promise: new Promise((resolve) => { const interval = setInterval(() => { const file = fs.readFileSync(pathname) if (!file) return const item = JSON.parse(file.toString()) if (!item) return if (item.progress === 100) { clearInterval(interval) resolve(item) } return }) }), }) }

    2. Defer is a feature of Remix that allows you to return an unresolved Promise from a loader. The page will server-side render without waiting for the promise to resolve, and then when it finally does, the client will re-render with the new data.
    1. ```js import { renderToReadableStream } from 'react-dom/server'; import type { EntryContext } from '@remix-run/cloudflare'; import { RemixServer } from '@remix-run/react'; import { renderHeadToString } from 'remix-island'; import { Head } from './root';

      const readableString = (value: string) => { const te = new TextEncoder(); return new ReadableStream({ start(controller) { controller.enqueue(te.encode(value)); controller.close(); }, }); };

      export default async function handleRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, remixContext: EntryContext, ) { const { readable, writable } = new TransformStream(); const head = readableString( <!DOCTYPE html><html><head>${renderHeadToString({ request, remixContext, Head, })}</head><body><div id="root">, ); const end = readableString(</div></body></html>);

      const body = await renderToReadableStream( <RemixServer context={remixContext} url={request.url} />, );

      Promise.resolve() .then(() => head.pipeTo(writable, { preventClose: true })) .then(() => body.pipeTo(writable, { preventClose: true })) .then(() => end.pipeTo(writable));

      responseHeaders.set('Content-Type', 'text/html');

      return new Response(readable, { status: responseStatusCode, headers: responseHeaders, }); } ```

    1. Pitfall #1: Server-Side Rendering Attacker-Controlled Initial State

      ```html

      <script>window.__STATE__ = ${JSON.stringify({ data })}</script>

      ```

    1. One option is to use the serialize-javascript NPM module to escape the rendered JSON.

      html { username: "pwned", bio: "</script><script>alert('XSS Vulnerability!')</script>" }

    2. This is risky because JSON.stringify() will blindly turn any data you give it into a string (so long as it is valid JSON) which will be rendered in the page. If { data } has fields that un-trusted users can edit like usernames or bios, they can inject something like this:

      json { username: "pwned", bio: "</script><script>alert('XSS Vulnerability!')</script>" }

    3. Sometimes when we render initial state, we dangerously generate a document variable from a JSON string. Vulnerable code looks like this:

      ```html

      <script>window.__STATE__ = ${JSON.stringify({ data })}</script>

      ```

    4. Server-side rendering attacker-controlled initial state
    1. To pass along the state, the template attaches state to window.__STATE__ inside a <script> tag.Now you can read state on the client side by accessing window.__STATE__.
  9. Feb 2023
    1. Node.js

      js import { renderToPipeableStream } from "react-dom/server.node"; import React from "react"; import http from "http"; const App = () => ( <html> <body> <h1>Hello World</h1> <p>This is an example.</p> </body> </html> ); var didError = false; http .createServer(function (req, res) { const stream = renderToPipeableStream(<App />, { onShellReady() { res.statusCode = didError ? 500 : 200; res.setHeader("Content-type", "text/html"); res.setHeader("Cache-Control", "no-transform"); stream.pipe(res); }, onShellError(error) { res.statusCode = 500; res.send( '<!doctype html><p>Loading...</p><script src="clientrender.js"></script>', ); }, onAllReady() { }, onError(err) { didError = true; console.error(err); }, }); }) .listen(3000);

      Deno

      ```js import { renderToReadableStream } from "https://esm.run/react-dom/server"; import * as React from "https://esm.run/react";

      const App = () => ( <html> <body>

      Hello World

      This is an example.

      </body> </html> );

      const headers = { headers: { "Content-Type": "text/html", "Cache-Control": "no-transform", }, };

      Deno.serve( async (req) => { return new Response(await renderToReadableStream(<App />), headers); }, { port: 3000 }, ); ```

      Bun

      ```js import { renderToReadableStream } from "react-dom/server"; const headers = { headers: { "Content-Type": "text/html", }, };

      const App = () => ( <html> <body>

      Hello World

      This is an example.

      </body> </html> );

      Bun.serve({ port: 3000, async fetch(req) { return new Response(await renderToReadableStream(<App />), headers); }, }); ```

    1. you're able to serve up the initial render (also called "fully hydrated")

      react initial render什么意思?SPA是初始化所有组件吗?跟路由有没有关系?vue 组件的按需加载与此类似吗?

  10. Dec 2022
    1. I had been wrapping my components with an improper tag that is, NextJS is not comfortable having a p tag wrapping your divs, sections etc so it will yell "Hydration failed because the initial UI does not match what was rendered on the server". So I solved this problem by examining how my elements were wrapping each other.
  11. Jun 2022
  12. Mar 2022
  13. Jan 2022
  14. Dec 2021
    1. Under-the-hood working of the streaming SSR server with the new React 14's suspense. A thread. #reactjs #webperf #perfmatters
  15. Oct 2021
    1. This function allows you to modify (or replace) a fetch request for an external resource that happens inside a load function that runs on the server (or during pre-rendering). For example, your load function might make a request to a public URL like https://api.yourapp.com when the user performs a client-side navigation to the respective page, but during SSR it might make sense to hit the API directly (bypassing whatever proxies and load balancers sit between it and the public internet).
  16. Jun 2021
  17. Sep 2020
  18. Mar 2020
    1. Javascript, APIs and Markup — this stack is all about finding middleground from the chaos of SSR+SPA. It is about stepping back and asking yourself, what parts of my page change and what parts don’t change?

      JavaScript, APIs and Markup (JAM Stack) - middleground between SSR + SPA.

      Advantages:

      • The parts that don’t change often are pre-rendered on the server and saved to static HTML files. Anything else is implemented in JS and run on the client using API calls.
      • Avoids too much data transfer (like the hydration data for SSR), therefore finds a good tradeoff to ship web content
      • Allows to leverage the power and cost of Content delivery networks (CDNs) to effectively serve your content
      • With serverless apps your APIs will never need a server to SSH into and manage
    2. Somewhere on this path to render pages on the fly (SSR) and render pages on the client (SPA) we forgot about the performance of our webpages. We were trying to build apps. But the web is about presenting content first and foremost!

      Website performance break with Client-side Rendering (SSR) and Single-page App (SPA)

  19. Dec 2019
  20. Nov 2018
  21. Oct 2018