Skip to content

9

Streaming

In the previous chapter, we discussed how the slow data fetches can impact the performance of your application. Let's look at how you can improve the user experience when there are slow data requests.

In this chapter...

Here are the topics we will cover:

What streaming is and when you might use it.

How to implement streaming with routeLoader$() et <Resource />.

How to implement streaming with useResource$() et <Resource />.

What loading skeletons are.

Where to place <Resource /> boundaries in your application.

What is streaming?

Streaming is a data transfer technique that allows you to gradually distribute data from the server to the client as soon as they are ready.

Diagram showing time with sequential data fetching and parallel data fetching

By streaming, you can prevent slow data requests from blocking your whole page. This allows the user to see and interact with parts of the page without waiting for all the data to load before any UI can be shown to the user.

Streaming works well with Qwik's component model, where each component can be considered a chunk.

There are two ways you implement streaming in Qwik:

Let's see how this works.

It’s time to take a quiz!

Test your knowledge and see what you’ve just learned.

What is streaming?

You must choose response !

How to implement streaming with routeLoader$() and <Resource />

For the moment, with only routeLoader$(), we have to wait for the data to be ready before we can display the page.

Waiting for the data to be ready before we can display the page.

However, there is a way to render the DOM up to the point where routeLoader$() is used and wait for it to complete. By returning an asynchronous function from our routeLoader$() we can stream/defer the rendering to provide immediate visual feedback.

To do this, we can use the <Resource /> component.
This example from the official Qwik documentation illustrates how to use <Resource /> withrouteLoader$().

Let's apply this concept to our application.

In our src/routes/dashboard/index.tsx file, we will replace the current routeLoader$ with a routeLoader$ that works with <Resource />.

Replace your current file with the following code: 👇

src/routes/dashboard/index.tsx

Here are some key points about this code:

  • We use useFetchData to fetch the data. This function returns an asynchronous function that fetches the data.

  • We use <Resource /> to render the data. The value prop is set to data, which is the result of calling useFetchData().

  • The onResolved callback is called when the data is successfully fetched. We destructure the data and render the components.

  • The onRejected callback is called if there is an error fetching the data. We render an error message.

  • The onPending callback is called while the data is being fetched. We render a loading message.

Now that we have implemented streaming with <Resource />, we can see the components that precede the <Resource /> component display before the data is ready. In our case, unlike before, the <SideNav /> and the <h1> are displayed immediately before the data is even loaded.

Immediately display <SideNav /> and <h1>Dashboard</h1>before the data is ready.

This is a great improvement in user experience as the user can see the page layout before the data is ready.

It’s time to take a quiz!

Test your knowledge and see what you’ve just learned.

Which Qwik component is used to handle streaming and manage loading states?

You must choose response !

The most observant will have noticed that the onPending, which should display a loading message, does not work. This is because the onPending callback is not yet supported with <Resource /> and routeLoader$.

Note: By browsing the official Qwik Discord and various forums, if I understand correctly, the use of onPending and onError with <Resource /> and routeLoader$ is not yet supported. It should be implemented in a future version of Qwik (V2.0).

Fortunately, there is another way to implement streaming in Qwik using useResource$().
With useResource$(), we can handle loading, error, and content display more flexibly.
This is what we will see in the next section.👇

How to implement streaming with useResource$() and <Resource />

In this section, we will see how to implement streaming with useResource$() and <Resource />.

We will use the same example as before, but this time we will use useResource$() instead of routeLoader$().

Unlike routeLoader$(), useResource$() must be declared in a component.

In our src/routes/dashboard/index.tsx file, replace your current file with the following code: 👇

src/routes/dashboard/index.tsx

Unlike routeLoader$(), onPending and onRejected are supported with useResource$() and <Resource />.
Note: Works only in SPA navigation (with the <Link /> component).👇

Display the onPending callback (Loading...) in SPA navigation mode when the data is being fetched.

Note: As specified in the documentation officielle de Qwik : "Fetching data as part of Server-Side Rendering (SSR) is a common and preferred method of data loading, typically handled by the routeLoader$()API. useResource$ is more of a low-level API that is useful when you want to fetch data in the browser."

Again, all this will depend on your needs and your context of use.🤔

Congratulations! You've just implemented streaming. But we can do more to improve the user experience. Let's show a loading skeleton instead of the Loading… text.

It’s time to take a quiz!

Test your knowledge and see what you’ve just learned.

What is the purpose of the onPending callback?

You must choose response !

Adding loading skeletons 💀

A loading skeleton is a simplified version of the UI. Many websites use them as a placeholder (or fallback) to indicate to users that the content is loading.

⚠️ Download skeletons.tsx file and place it in the `src/components/ui/` folder:

In our src/global.css file, add the animation for the loading skeleton:

src/global.css

In our src/routes/dashboard/index.tsx file, we will replace the Loading… text with a loading skeleton.

src/routes/dashboard/index.tsx

Now, when the data is being fetched, the loading skeleton ☠️ will be displayed instead of the Loading… text.👇

Display the loading skeleton when the data is being fetched.

Streaming a component

So far, you're streaming a whole page. But you can also be more granular and stream specific components using <Resources />.

If you remember the slow data request, fetchRevenue(), this is the request that is slowing down the whole page. Instead of blocking your whole page, you can use <Resources /> to stream only this component and immediately show the rest of the page's UI.

To do so, you'll need to move the data fetch to the component, let's update the code to see what that'll look like:

Delete all instances of fetchRevenue() and its data from src/routes/dashboard/index.tsx: 👇

src/routes/dashboard/index.tsx

Now, we will reuse useResource$() and <Resource /> inside our <RevenueChart /> component.

We use <RevenueChartSkeleton /> to display a loading revenue chart skeleton while the data is being fetched.

src/components/ui/dashboard/revenue-chart.tsx

Now, the <RevenueChart /> component fetches its own data and display it dynamically according to the loading state independently. 🚀

Show result in the browser and see the difference: 👇

The <RevenueChart /> component fetches its own data independently and displays it dynamically according to the loading state.

Now, you can remove the intentionnal delay in the fetchRevenue() function in the src/lib/data.ts file.👇

src/lib/data.ts

Practice: Streaming <LatestInvoices>

Now it's your turn! Practice what you've just learned by streaming the <LatestInvoices> component.

Move the data fetching and display logic from the src/routes/dashboard/index.tsx file to the src/components/ui/dashboard/latest-invoices.tsx file.

Once you're ready, expand the toggle to see the solution code:

Grouping components

Great job! You're almost there, it's time to group the <Card /> components. You can fetch data for each individual card, but this could lead to a popping effect as the cards load in, this can be visually jarring for the user.

So, how would you tackle this problem?

To create more of a staggered effect, you can group the cards using a wrapper component. This means the static <SideNav/> will be shown first, followed by the cards, etc.

In your src/routes/dashboard/index.tsx file:

  1. Delete useResource$() function.
  2. Delete <Resource /> component.
  3. Import a new wrapper component called <CardWrapper />.
  4. Replace the <Card /> components with <CardWrapper />.

Your src/routes/dashboard/index.tsx file should look like this: 👇

src/routes/dashboard/index.tsx

In your src/components/ui/dashboard/cards.tsx file:

  1. Import <Resource />, useResource$() and fetchCardData().
  2. Import CardsSkeleton component.
  3. Create a CardsWrapper component.
  4. In the CardsWrapper component, fetch the card data using useResource$().
  5. Use <Resource /> to display the card data.

Your src/components/ui/dashboard/cards.tsx file should look like this: 👇

src/components/ui/dashboard/cards.tsx

You should see all the cards load in at the same time. You can use this pattern when you want multiple components to load in at the same time.

Great! We now have 3 components that load their data and display it according to their loading state completely independently. 🚀

Deciding where to place your <Resource /> boundaries

Where your place your <Resource /> boundaries will depend on a few things:

  1. How you want the user experience the page as it streams.
  2. What content you want prioritize.
  3. If the components rely on data fetching.

Take a look at your dashboard page, is there anything you would've done differently?

Don't worry. There isn't a right answer.

  • You could have chosen to stream the whole page, like we did at the beginning.
  • You could also create a staggered effect by streaming page sections. But you'll need to create wrapper components.
  • You could stream every component individually...

Where you place your <Resource /> boundaries will vary depending on your application. In general, it's good practice to move your data fetching logic to the component level. This way, you can have more control over the loading state of your components. But there is nothing wrong with streaming the sections or the whole page if that's what your application needs.

Don't be afraid to experiment and see what works best for your users.

It’s time to take a quiz!

Test your knowledge and see what you’ve just learned.

In general, what is considered good practice when working with useResource$(), <Resource /> and data fetching?

You must choose response !

Source code

You can find the source code for chapter 9 on GitHub.

You've Completed Chapter 9

You've learned how to stream components with <Resource /> and loading skeletons.

Next Up

10: Adding Search and Pagination

Learn how to implement search and pagination in your application.