Skip to content

How To Authenticate Your SolidJS Routes With Solid Router

This article was written over 18 months ago and may contain information that is out of date. Some content may be relevant but please refer to the relevant official documentation or available resources for the latest information.

In this post, we will talk about how we can authenticate our SolidJS route with Solid Router. By the end of this article, we will understand what authentication is, and how it is used to guard routes in SolidJS using the Solid Router.

Overview

Route authentication has been around for a while, and is now a fundamental part of many applications like Facebook, Gmail, and Instagram. At this point, it’s about as ubiquitous as entering a password into a phone. Authentication through route guards in SolidJS can be achieved in two different ways. The first that we are going to cover in this app is the use of a Solid Router. The second is provided by Solid Start, which is more like a folder-based routing. It is a meta-framework (a framework built on another framework). At its core, it's powered by SolidJS and Solid Router. This article assumes that you have already set up a SolidJS project, and will not walk through that part of the process. If you have not yet spun up a project, you can find tutorials and instructions at SolidJS.

What is Authentication?

Authentication is a process of verifying the identity of the user. It provides access control to check if the user is authenticated. Authentication enables organizations to keep their networks secure by permitting only authenticated users or processes to gain access to their protected resources which, in our case, is the route in the application.

To learn more about authentication, you can check out the following;

What is a route guard?

A route guard helps to prevent unauthenticated users from accessing certain routes/parts in an application. For example, an app lock on a mobile phone can be seen as a guard. You may be able to access the phone and some apps, but you can’t access other apps.

We can also use parental controls on the phone, internet, and so on as a type of guard. You can read more about route guard.

Get Started

If you haven't installed the Solid Router in your project, run the command below:


  # Using NPM
  npm i @SolidJS/router

  # Using PNPM
  pnpm i @SolidJS/router

  # Using yarn
  yarn add @SolidJS/router

Solid Router is a universal router for SolidJS which works whether you're rendering on the client or the server. It was inspired by and combines the paradigms of React Router and the Ember Router. To read more, check out Solid Router

Having installed @SolidJS/router, we will be creating the following files;

  • Home: src/pages/Home.jsx
  • Signin: src/pages/Signin.jsx
  • Pricing: src/pages/Pricing.jsx
  • RouteGuard: src/RouteGuard/index.jsx
  • Header: src/components/Header/index.jsx
  • Header styles: src/components/Header/Header.module.css

Terminologies

  • useNavigate: a library provided to us by SolidJS Router that is used to navigate.
  • createEffect: creates a listener, which calls it's first argument when attached signals change.
  • Outlet: serves as the children associated with RouteGuard wrapper
  • NavLink: used to navigate between pages just like a tag which takes a property href for the page route path.

Home page

Below is the code we will paste in our home page file. But if you already have a content for this, you don't have to change it.

import appstyles from '../App.module.css';
export default function Home() {
  return (
    <div class={appstyles.wrapper}>
      <h2>Home page here</h2>
      <p>You can see this because you are authenticated</p>
    </div>
  );
}

This is just a simple page component with a style imported at the top.

Signin page

This is the content of our sign in page, but if have a content already, that's fine. The most important contents are the logIn function and the createEffect.

import { useNavigate } from "@SolidJS/router";
import { createEffect } from "solid-js";
import appstyles from "../App.module.css";

export default function SignIn () {
  const navigate = useNavigate();
  const logIn = () => {
    sessionStorage.setItem('token', 'mytokenisawesome');
    navigate('/home', { replace: true });
  };

  createEffect(() => {
    if(sessionStorage.getItem('token')) {
      navigate('/home', { replace: true })
    }
  })
  return (
    <div class={appstyles.wrapper}>
      <h2>Sign In Page</h2>
      <p>You can see this because you are not authenticated</p>
      <button class={appstyles.login_btn} onClick={logIn}>Log In</button>
    </div>
  )
}

The logIn function will be called when the login button is clicked. When clicked, a hardcoded token is saved in the session storage. The user is then navigated to the home page, which by default, can only be accessed if the user has been authenticated. The createEffect ensures the authenticated user won't access the sign in page.

Pricing page

This is just another page which, in this project, represents another protected page that could only be accessed by an authenticated user.


import appstyles from "../App.module.css";

export default function Pricing() {
  return (
    <div class={appstyles.wrapper}>
      <h2>Pricing page here</h2>
      <p>You can see this because you are authenticated</p>
      <i>Here you can find all prices</i>
    </div>
  );
}

This is just a simple page component with a style imported at the top.

RouteGuard Component


import { Outlet, useNavigate } from "@SolidJS/router";
import { createEffect } from "solid-js";
import Header from "../components/Header";

export default function RouteGuard () {
  const navigate = useNavigate();
  const token = sessionStorage.getItem('token');

  createEffect(() => {
    if(!token) {
      navigate('/signin', { replace: true });
    }
  })

  return (
    <div>
      <Header />
      <Outlet />
    </div>
  )
}

This serves as the wrapper for routes you want to protect from unauthenticated users. If there is no token in the session storage, the user will be routed to the signin page.

Header component

If you already have the content for the header file, you don't have to change it. All you need is the logOut function.


import { NavLink, useNavigate } from "@SolidJS/router";
import styles from './Header.module.css';

export default function Header() {
  const navigate = useNavigate();

  const logOut = () => {
    sessionStorage.removeItem('token');
    navigate('/signin', { replace: true });
  }
  return (
    <div class={styles.container}>
      <h3>My SolidJS App</h3>
      <div class={styles.links}>
        <NavLink href="/home">
          Home
        </NavLink>
        <NavLink href="/pricing">
          Pricing
        </NavLink>
      </div>
      <button class={styles.logout_btn} onClick={logOut}>Log out</button>
    </div>
  );
}

Above, we have styles imported from the header CSS module, and we have a logOut function that is used to remove the token from the session storage and also navigate the user to the signin page.

Header styles

This is the style for the header component.

.container {
  padding: 0.5rem 1rem;
  background-color: rgb(34, 80, 130);
  color: #fff;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.links {
  color: #fff;
  display: flex;
  flex: 1;
  align-items: center;
  justify-content: flex-end;
  gap: 1rem;
  font-size: 1rem;
  text-decoration: none;
  padding: 0 1.5rem;
  text-transform: uppercase;
}
.links a:link, a {
  color: #fff;
  text-decoration: none;
}

.logout_btn {
  padding: 0.5rem 0.86rem;
  background-color: #fff;
  color: rgba(255, 0, 0, 0.795);
  text-transform: uppercase;
  border: 0;
  outline: none;
  border-radius: 0.3rem;
  font-weight: 800;
  cursor: pointer;
  transition: background-color 0.2s ease-in-out;
}

.logout_btn:hover {
  background-color: rgb(239, 236, 236);
}

Updates for src/App.jsx

This file is where the route is managed, and where we grouped certain routes we wanna guard or protect from unauthenticated users.

import { Route, Routes } from '@SolidJS/router';
import Home from './pages/Home';
import SignIn from './pages/Signin';
import RouteGuard from './RouteGuard';
import Pricing from './pages/Pricing';

function App() {
  return (
    <Routes>
      <Route path="/signin" component={SignIn} />
      <Route path="/" component={RouteGuard}>
        <Route path="/home" component={Home} />
        <Route path="/pricing" component={Pricing} />
      </Route>
      <Route path="*" element={()=> <div>Page Not found!!!</div>} />
    </Routes>
  );
}

export default App;

Update for src/App.module.css


.login_btn {
  padding: 0.5rem 0.86rem;
  background-color: rgb(34, 80, 130);
  color: #fff;
  text-transform: uppercase;
  border: 0;
  outline: none;
  border-radius: 0.3rem;
  font-weight: 800;
  cursor: pointer;
  transition: background-color 0.2s ease-in-out;
}

.login_btn:hover {
  background-color: rgb(27, 64, 104);
}

.wrapper {
  padding: 1rem;
}

Updates for src/index.jsx

We have to update this file by wrapping our App with Router


/* @refresh reload */
import { render } from 'solid-js/web';
import { Router } from '@SolidJS/router';

import './index.css';
import App from './App';

render(() => (
  <Router>
    <App />
  </Router>
), document.getElementById('root'));

Update for src/index.css

a.active, a:active {
  text-decoration: underline !important;
  text-decoration-line: underline;
  text-decoration-style: wavy !important;
  text-decoration-color: rgba(255, 0, 0, 0.795) !important;
  text-underline-offset: 2px;
  text-decoration-thickness: from-font;
}

Conclusion

In this article, we were able to understand what authentication is, and how to create a route guard which is very important when trying to prevent certain users from accessing certain routes in our application. I hope we have been able to help you see the possibilities of making use of SolidJS and Solid Router to build a well-authenticated application. If you are having issues, you can check out the repo for SolidJS-route-guard. Don't forget that you can see, in detail, how this was maximized in one of This Dot's open source project starter.dev GitHub Showcases.

Happy coding!

This Dot is a consultancy dedicated to guiding companies through their modernization and digital transformation journeys. Specializing in replatforming, modernizing, and launching new initiatives, we stand out by taking true ownership of your engineering projects.

We love helping teams with projects that have missed their deadlines or helping keep your strategic digital initiatives on course. Check out our case studies and our clients that trust us with their engineering.

You might also like

How to Create Your Own Custom Renderer in SolidJS cover image

How to Create Your Own Custom Renderer in SolidJS

Intro In this article, we will explore what custom renderers are, the problem they solve, and how SolidJS has created a custom renderer that is quite unique because of its simplicity and ease of use. We will also learn how to create a custom renderer in SolidJS, and how to use it in our applications. Custom renderers have a long history with all the major frameworks in the market like Renderer2 in Angular, createRenderer in Vue, and React Reconciler in React. They are used to build awesome technologies and mind-blowing libraries. SolidJS recently added the ability to create a custom renderer with Solid Universal Renderer. > Note: We also have a great article about How to create a custom renderer for React that you can check out! Custom Renderers in SolidJS To better understand how custom renderers in SolidJS work, we have to understand it first in React and Vue (VDOM frameworks). In React, the library itself (React.js) doesn’t know anything about the DOM. It doesn’t understand what a div or h1 means. React converts all of the components into a JavaScript object that represents the UI in the memory (Virtual DOM), and then update the related nodes when the state changes in specific components. That’s it! This is how React and Vue work under the hood. The React Renderer (which is a different package called “react-dom”) then works on rendering this Virtual DOM on the screen, and updates it when it receives updates from React.js. The same thing happens in Vue. From here, the custom renderers have come to live. You aren’t limited to using only the official Renderer for the framework, but you can also build your own.. We will learn more about them in the proceeding sections. But for now, you just need to know that there is no virtual DOM in SolidJS. But it modifies directly on the DOM. This is the only difference between the Custom Renderers in SolidJS and React or Vue. Real-world Custom Renderers In this section, I’ll show you the most popular custom renderers that are used in many production applications in different frameworks. One of the most popular custom renderers in the industry is React Native (yes, it’s a React custom renderer for Android and iOS platforms). React-three-fiber is one of the most famous examples of React Reconciler in production. It converts the JSX VDOM to WebGL graphics. React-pdf is a React renderer for creating PDF files on the browser and server. TroisJS is a Vue custom renderer for Three.js and WebGL. There are many, many more examples. HTML Nodes Everything you see on the screen in the web browser is a node; the HTML element is a node, the attribute of that HTML element is a node, for example: ` Also, the text between and opening and closing tags of this HTML element is a node: ` Let’s code a SolidJS custom renderer The process is going to be similar to React, but SolidJS Universal is even easier than React Reconciler because it has fewer options. Create a new file to write our renderer and import the createRenderer from SolidJS universal: ` Second, let’s create an empty renderer: ` Renderer Options Let’s break all of these options down in detail: createElement This option is responsible for handling the creation of a new element node. It takes only one parameter which is the element type and it could be something like h1, p, divetc ` And here we can play with the JSX elements for fun. For example, we can add our custom elements like: ` Technically, there is no HTML component called customLikeButton. But here in this option, we can resolve this type as we desire. createTextNode This option is responsible for handling creating a new text node. It takes only one parameter which is the text itself. ` replaceText This option is responsible for handling updating the text node when it gets updates. It takes two parameters; the actual text node that has been updated, and the new text. ` insertNode This option is responsible for inserting or injecting a new node in the UI and the DOM. It takes three parameters, the parent node, the node itself, and a reference node (anchor) in the parent node. ` removeNode This option is responsible for removing a node from the DOM. It takes two parameters, the parent node, and the node which needs to be removed ` setProperty This one is a little bit more complicated than the previous ones because it handles more categories. It is responsible for handling the attributes or properties of the element such as the classNames, style, and events like onClick and onSubmit. ` isTextNode This option is used internally to determine if this node is a text node or not. ` > Note: the node.type in the line above is a pure JavaScript code. In JavaScript nodes have 12 types with 12 numbers each representing a type. For example, 1 represents an element node, and 2 represents an attribute node. For more info see the HTML DOM Element nodeType on W3Schools and Node.nodeType - Web APIs | MDN. getParentNode This option is being used internally in SolidJS renderer to get the parent node of a given node. ` getFirstChild This option is being used internally in SolidJS renderer to get the first node of a given parent node. ` getNextSibling This option is being used internally in SolidJS renderer to get the next node of a given node in the DOM tree. ` Renderer method There are a lot of methods for this renderer, but the most important one is render which we will replace the SolidJS web one with. ` There are additional methods available: - effect - memo - createComponent - createElement - createTextNode - insertNode - insert - spread - setProp - mergeProps The performance of SolidJS Universal Renderer Ryan Carniato, the creator of SolidJS, created a test on the SolidJS Universal Renderer in his stream of Benchmarking and Custom Renderers on his YouTube channel, and it was pretty interesting. It wasn’t much slower than the SolidJS web renderer at only a few milliseconds slower. To test it, you can use the js-framework-benchmark repo, which is an amazing tool you can use to compare the performance of different JavaScript frameworks. Conclusion In this article, we learned about the custom renderers in SolidJS and how to create one. We also learned about the performance of SolidJS Universal Renderer and how it compares to the SolidJS web renderer. I hope you enjoyed this article and learned something new. For more SolidJS resources, check out our solidjs.framework.dev where you can find all cool courses, tutorials, and libraries for SolidJS. Also you can create your next SolidJS project, try our SolidJS starter kit from Starter.dev it has a lot of tools pre-configured for you. If you have any questions or suggestions, please feel free to send them to us or reach out to us on Twitter....

Sharing Signals and Stores: Context API in SolidJS cover image

Sharing Signals and Stores: Context API in SolidJS

Introduction Welcome to our latest blog post on SolidJS, where we delve into the world of the Context API. Context API is a popular and versatile feature in SolidJS, allowing developers to pass data and functions through the component tree without the need for props drilling. In this post, we will discuss how SolidJS implements the Context API, including the creation of contexts, sharing Signals and stores, and utilizing them within the components. Whether you are a seasoned developer, or just starting out, this post will provide valuable insights into the use of Context API in SolidJS and what are the advantages in the Reactivity ecosystem. So sit back, grab a cup of coffee, and let's dive in! What is SolidJS? SolidJS is a unique and powerful JavaScript framework that is quickly gaining popularity among developers. One of the key features that sets SolidJS apart from other frameworks is its reactive nature. How about reactivity? Reactivity is a programming paradigm through which the application automatically updates and re-renders when the data changes. This means that developers do not have to manually update the view when the data changes, saving a lot of time and effort. SolidJS achieves this reactivity by using a virtual DOM which is a lightweight representation of the actual DOM. This virtual DOM is then used to update the actual DOM, making the process of updating the view much faster and more efficient. Performance Another advantage of SolidJS is its performance. SolidJS is designed to be fast and efficient thanks to its use of a virtual DOM and a functional programming approach. By using functional components and immutability, SolidJS is able to optimize the process of re-rendering, resulting in a smoother and more responsive user experience. In addition to its reactivity and performance, SolidJS also has a small footprint and a simple API. The framework is lightweight and easy to learn, making it a great choice for developers of all skill levels. Signals: SolidJS Signals are a powerful feature that allow for easy communication between different parts of your application. They are similar to events or hooks in other frameworks, but with a few key differences. One of the key advantages of Signals in SolidJS is that they are fully reactive, meaning that they automatically update and re-render when the data changes. This makes it easy to create responsive and dynamic applications without having to manually update the view. To use Signals in SolidJS, you need to first create a signal by using the createSignal function. ` Store In SolidJS, a store works similar to other frontend frameworks. To keep things light, SolidJS creates underlying Signals only for properties that are accessed under tracking scopes. All Signals in Stores are created lazily as requested. The createStore call takes the initial value, and returns a read/write tuple similar to Signals. The first element is the store proxy which is readonly, and the second is a setter function. What About context? SolidJS provides a context API to pass data around without relying on passing props; this is useful in sharing Signals and Stores as described above. According to SolidJS docs: “Using Context has the benefit of being created as part of the reactive system and managed by it.” Getting Started: First, let’s create our context: ` Then we can consume our recently Context created: ` To use context, we'll first wrap our App component in order to provide it globally: ` We can now consume the context and our components will look like this: ` ` Conclusion In this article, we were able to use the contextAPI to share the props we want across the app, and benefit from the performance of Reactivity because of that. This will make our application more readable, but also brings some additional benefits to the app which are as follows: - avoid passing props down through multiple levels of the component tree. - reducing the number of re-renders that occur when props are passed down. - by isolating state and props to a specific context, you can make your code more organized and easier to understand. - components that use the Context API are more easily tested. Would you like to learn more about Signals and Stores, or SolidJS in general? You can see in detail how this was maximized in one of This Dot Labs' open source project starter.dev GitHub Showcases....

Mocking REST API in Unit Test Using MSW cover image

Mocking REST API in Unit Test Using MSW

Unit testing has become an integral part of application development, and in this discussion, we will be talking about mocking API while unit testing using MSW, a tool used for mocking APIs. We also won't be setting the test environment up since we expect you have already done so, and also we will be using a React project for our testing. Please note: the setup will work for any framework of your choice. What is MSW? MSW (Mock Service Worker) is an API mocking library that uses Service Worker API to intercept actual requests. Why Mock? Well, it avoids us making an actual HTTP request by leveraging on mock server, and a service worker which, in turn, prevents any form of break should something go wrong with the server you would have sent a request to. What is a Mock Server? A mock server imitates a real API server by returning the mock API responses to the API requests. This will help intercept the API request. You can read more here. Project Setup We will be installing few packges, and doing a little configuration. Installations We will be installing the following - msw: This package helps use to mock the API request and also helps us set up the server. - whatwg-fetch: Not always required, but what it does is it makes fetch available if you are useing the fetch API. Sometimes you get this: ` Creating the mock config Lets create a mock folder in our src folder to keep things organized. We want to have two files in the mock folder in this example: - postHandler.js: Here, we will have the handler that will help with intercepting the API. - serverSetup.js: Here we will set up or server that will house the handler. postHandler.js content Its important that the API url matches the url you intend to intercept: ` Lets explain the above code: posts: This is dummy data that is going to serve as our response data knowing full well what the data structure might look like. postHandler: This will serve as the handler, which will help in intercepting the request made to that url. serverSetUp.js content ` From the above code, the setupServer can take in any number of handlers separated by comma. The setupServer serves as our "fake" server where we can add as many handlers to intercept requests. Component Lets create a component for the purpose of our test. Here, I will be making use of the App.js file. My code will look like this: ` All we are doing is fetching some data from the URL when the component mounts and saving the data in our state to display them. And we are using the jsonplacehoder url to fetch posts. Test the component We will create a test file named App.spec.js with this contents ` We imported the component we want to test with: the whatwg-fetch to prevent us with the possible error we talked about. Then, we imported our server. There are few methods above: - beforeAll: This runs before all tests are executed. - afterAll: The opposite of beforeAll. - afterEach: As the name implies, it runs after each test. - resetHandler: It is a useful clean up mechanism between multiple test suites that leverage runtime request handlers. If you added another handler during a test, this resetHandler will remove that handler so that the next test will not know about it. You can see an example of reset handler. Though, in our code example, we didn't need it. But its important to know we have such control and power over what we want to do. So, in summary before all listen to the server for any request, after all close the server and after each test reset handler. On app render there is an API call, but our server will intercept it, so at first the loading text will exist. Then after, it won't. Will it work without intercepting? Yes it will. So, why do all of this? - Anything could happen to the API server, so you don't want you app test to fail. - The data could change, and we don't want a situation where the test fails because of data changes. Conclusion We have created a repo that you can test with in case you have issues following along. Please feel free to let us know what you think as we look forward to building a stronger community by sharing knowledge....

Next.js Rendering Strategies and how they affect core web vitals cover image

Next.js Rendering Strategies and how they affect core web vitals

When it comes to building fast and scalable web apps with Next.js, it’s important to understand how rendering works, especially with the App Router. Next.js organizes rendering around two main environments: the server and the client. On the server side, you’ll encounter three key strategies: Static Rendering, Dynamic Rendering, and Streaming. Each one comes with its own set of trade-offs and performance benefits, so knowing when to use which is crucial for delivering a great user experience. In this post, we'll break down each strategy, what it's good for, and how it impacts your site's performance, especially Core Web Vitals. We'll also explore hybrid approaches and provide practical guidance on choosing the right strategy for your use case. What Are Core Web Vitals? Core Web Vitals are a set of metrics defined by Google that measure real-world user experience on websites. These metrics play a major role in search engine rankings and directly affect how users perceive the speed and smoothness of your site. * Largest Contentful Paint (LCP): This measures loading performance. It calculates the time taken for the largest visible content element to render. A good LCP is 2.5 seconds or less. * Interaction to Next Paint (INP): This measures responsiveness to user input. A good INP is 200 milliseconds or less. * Cumulative Layout Shift (CLS): This measures the visual stability of the page. It quantifies layout instability during load. A good CLS is 0.1 or less. If you want to dive deeper into Core Web Vitals and understand more about their impact on your website's performance, I recommend reading this detailed guide on New Core Web Vitals and How They Work. Next.js Rendering Strategies and Core Web Vitals Let's explore each rendering strategy in detail: 1. Static Rendering (Server Rendering Strategy) Static Rendering is the default for Server Components in Next.js. With this approach, components are rendered at build time (or during revalidation), and the resulting HTML is reused for each request. This pre-rendering happens on the server, not in the user's browser. Static rendering is ideal for routes where the data is not personalized to the user, and this makes it suitable for: * Content-focused websites: Blogs, documentation, marketing pages * E-commerce product listings: When product details don't change frequently * SEO-critical pages: When search engine visibility is a priority * High-traffic pages: When you want to minimize server load How Static Rendering Affects Core Web Vitals * Largest Contentful Paint (LCP): Static rendering typically leads to excellent LCP scores (typically &lt; 1s). The Pre-rendered HTML can be cached and delivered instantly from CDNs, resulting in very fast delivery of the initial content, including the largest element. Also, there is no waiting for data fetching or rendering on the client. * Interaction to Next Paint (INP): Static rendering provides a good foundation for INP, but doesn't guarantee optimal performance (typically ranges from 50-150 ms depending on implementation). While Server Components don't require hydration, any Client Components within the page still need JavaScript to become interactive. To achieve a very good INP score, you will need to make sure the Client Components within the page is minimal. * Cumulative Layout Shift (CLS): While static rendering delivers the complete page structure upfront which can be very beneficial for CLS, achieving excellent CLS requires additional optimization strategies: * Static HTML alone doesn't prevent layout shifts if resources load asynchronously * Image dimensions must be properly specified to reserve space before the image loads * Web fonts can cause text to reflow if not handled properly with font display strategies * Dynamically injected content (ads, embeds, lazy-loaded elements) can disrupt layout stability * CSS implementation significantly impacts CLS—immediate availability of styling information helps maintain visual stability Code Examples: 1. Basic static rendering: ` 2. Static rendering with revalidation (ISR): ` 3. Static path generation: ` 2. Dynamic Rendering (Server Rendering Strategy) Dynamic Rendering generates HTML on the server for each request at request time. Unlike static rendering, the content is not pre-rendered or cached but freshly generated for each user. This kind of rendering works best for: * Personalized content: User dashboards, account pages * Real-time data: Stock prices, live sports scores * Request-specific information: Pages that use cookies, headers, or search parameters * Frequently changing data: Content that needs to be up-to-date on every request How Dynamic Rendering Affects Core Web Vitals * Largest Contentful Paint (LCP): With dynamic rendering, the server needs to generate HTML for each request, and that can't be fully cached at the CDN level. It is still faster than client-side rendering as HTML is generated on the server. * Interaction to Next Paint (INP): The performance is similar to static rendering once the page is loaded. However, it can become slower if the dynamic content includes many Client Components. * Cumulative Layout Shift (CLS): Dynamic rendering can potentially introduce CLS if the data fetched at request time significantly alters the layout of the page compared to a static structure. However, if the layout is stable and the dynamic content size fits within predefined areas, the CLS can be managed effectively. Code Examples: 1. Explicit dynamic rendering: ` 2. Simplicit dynamic rendering with cookies: ` 3. Dynamic routes: ` 3. Streaming (Server Rendering Strategy) Streaming allows you to progressively render UI from the server. Instead of waiting for all the data to be ready before sending any HTML, the server sends chunks of HTML as they become available. This is implemented using React's Suspense boundary. React Suspense works by creating boundaries in your component tree that can "suspend" rendering while waiting for asynchronous operations. When a component inside a Suspense boundary throws a promise (which happens automatically with data fetching in React Server Components), React pauses rendering of that component and its children, renders the fallback UI specified in the Suspense component, continues rendering other parts of the page outside this boundary, and eventually resumes and replaces the fallback with the actual component once the promise resolves. When streaming, this mechanism allows the server to send the initial HTML with fallbacks for suspended components while continuing to process suspended components in the background. The server then streams additional HTML chunks as each suspended component resolves, including instructions for the browser to seamlessly replace fallbacks with final content. It works well for: * Pages with mixed data requirements: Some fast, some slow data sources * Improving perceived performance: Show users something quickly while slower parts load * Complex dashboards: Different widgets have different loading times * Handling slow APIs: Prevent slow third-party services from blocking the entire page How Streaming Affects Core Web Vitals * Largest Contentful Paint (LCP): Streaming can improve the perceived LCP. By sending the initial HTML content quickly, including potentially the largest element, the browser can render it sooner. Even if other parts of the page are still loading, the user sees the main content faster. * Interaction to Next Paint (INP): Streaming can contribute to a better INP. When used with React's &lt;Suspense />, interactive elements in the faster-loading parts of the page can become interactive earlier, even while other components are still being streamed in. This allows users to engage with the page sooner. * Cumulative Layout Shift (CLS): Streaming can cause layout shifts as new content streams in. However, when implemented carefully, streaming should not negatively impact CLS. The initially streamed content should establish the main layout, and subsequent streamed chunks should ideally fit within this structure without causing significant reflows or layout shifts. Using placeholders and ensuring dimensions are known can help prevent CLS. Code Examples: 1. Basic Streaming with Suspense: ` 2. Nested Suspense boundaries for more granular control: ` 3. Using Next.js loading.js convention: ` 4. Client Components and Client-Side Rendering Client Components are defined using the React 'use client' directive. They are pre-rendered on the server but then hydrated on the client, enabling interactivity. This is different from pure client-side rendering (CSR), where rendering happens entirely in the browser. In the traditional sense of CSR (where the initial HTML is minimal, and all rendering happens in the browser), Next.js has moved away from this as a default approach but it can still be achievable by using dynamic imports and setting ssr: false. ` Despite the shift toward server rendering, there are valid use cases for CSR: 1. Private dashboards: Where SEO doesn't matter, and you want to reduce server load 2. Heavy interactive applications: Like data visualization tools or complex editors 3. Browser-only APIs: When you need access to browser-specific features like localStorage or WebGL 4. Third-party integrations: Some third-party widgets or libraries that only work in the browser While these are valid use cases, using Client Components is generally preferable to pure CSR in Next.js. Client Components give you the best of both worlds: server-rendered HTML for the initial load (improving SEO and LCP) with client-side interactivity after hydration. Pure CSR should be reserved for specific scenarios where server rendering is impossible or counterproductive. Client components are good for: * Interactive UI elements: Forms, dropdowns, modals, tabs * State-dependent UI: Components that change based on client state * Browser API access: Components that need localStorage, geolocation, etc. * Event-driven interactions: Click handlers, form submissions, animations * Real-time updates: Chat interfaces, live notifications How Client Components Affect Core Web Vitals * Largest Contentful Paint (LCP): Initial HTML includes the server-rendered version of Client Components, so LCP is reasonably fast. Hydration can delay interactivity but doesn't necessarily affect LCP. * Interaction to Next Paint (INP): For Client Components, hydration can cause input delay during page load, and when the page is hydrated, performance depends on the efficiency of event handlers. Also, complex state management can impact responsiveness. * Cumulative Layout Shift (CLS): Client-side data fetching can cause layout shifts as new data arrives. Also, state changes might alter the layout unexpectedly. Using Client Components will require careful implementation to prevent shifts. Code Examples: 1. Basic Client Component: ` 2. Client Component with server data: ` Hybrid Approaches and Composition Patterns In real-world applications, you'll often use a combination of rendering strategies to achieve the best performance. Next.js makes it easy to compose Server and Client Components together. Server Components with Islands of Interactivity One of the most effective patterns is to use Server Components for the majority of your UI and add Client Components only where interactivity is needed. This approach: 1. Minimizes JavaScript sent to the client 2. Provides excellent initial load performance 3. Maintains good interactivity where needed ` Partial Prerendering (Next.js 15) Next.js 15 introduced Partial Prerendering, a new hybrid rendering strategy that combines static and dynamic content in a single route. This allows you to: 1. Statically generate a shell of the page 2. Stream in dynamic, personalized content 3. Get the best of both static and dynamic rendering Note: At the time of this writing, Partial Prerendering is experimental and is not ready for production use. Read more ` Measuring Core Web Vitals in Next.js Understanding the impact of your rendering strategy choices requires measuring Core Web Vitals in real-world conditions. Here are some approaches: 1. Vercel Analytics If you deploy on Vercel, you can use Vercel Analytics to automatically track Core Web Vitals for your production site: ` 2. Web Vitals API You can manually track Core Web Vitals using the web-vitals library: ` 3. Lighthouse and PageSpeed Insights For development and testing, use: * Chrome DevTools Lighthouse tab * PageSpeed Insights * Chrome User Experience Report Making Practical Decisions: Which Rendering Strategy to Choose? Choosing the right rendering strategy depends on your specific requirements. Here's a decision framework: Choose Static Rendering when * Content is the same for all users * Data can be determined at build time * Page doesn't need frequent updates * SEO is critical * You want the best possible performance Choose Dynamic Rendering when * Content is personalized for each user * Data must be fresh on every request * You need access to request-time information * Content changes frequently Choose Streaming when * Page has a mix of fast and slow data requirements * You want to improve perceived performance * Some parts of the page depend on slow APIs * You want to prioritize showing critical UI first Choose Client Components when * UI needs to be interactive * Component relies on browser APIs * UI changes frequently based on user input * You need real-time updates Conclusion Next.js provides a powerful set of rendering strategies that allow you to optimize for both performance and user experience. By understanding how each strategy affects Core Web Vitals, you can make informed decisions about how to build your application. Remember that the best approach is often a hybrid one, combining different rendering strategies based on the specific requirements of each part of your application. Start with Server Components as your default, use Static Rendering where possible, and add Client Components only where interactivity is needed. By following these principles and measuring your Core Web Vitals, you can create Next.js applications that are fast, responsive, and provide an excellent user experience....

Let's innovate together!

We're ready to be your trusted technical partners in your digital innovation journey.

Whether it's modernization or custom software solutions, our team of experts can guide you through best practices and how to build scalable, performant software that lasts.

Prefer email? hi@thisdot.co