Skip to content

How to Create Your Own Custom Renderer in SolidJS

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.

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:

<img src="logo.png" /> // <- this node’s type is an element
	// ^
	// this attribute is a node

Also, the text between and opening and closing tags of this HTML element is a node:

<h1>  // <- this h1 is type element
	Hello world  // <- this text is type text and it's a separate node
</h1>

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:

import { createRenderer } from 'solid-js/universal';

Second, let’s create an empty renderer:

const renderer = createRenderer({
	// Add the renderer properties here
})

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

createElement(type) {
    return document.createElement(type);
}

And here we can play with the JSX elements for fun. For example, we can add our custom elements like:

function component() {
	return (
    <div>
	    <customLikeButton />
    </div>
	)
}

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.

createTextNode(text) {
    return document.createTextNode(text);
}

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.

replaceText(node, text) {
    node.data = 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.

insertNode(parent, node, anchor) {
    parent.insertBefore(node, anchor);
}

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

removeNode(parent, node) {
    parent.removeChild(node);
}

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.

setProperty(node, name, value) {
    if (name === "style") Object.assign(node.style, value);
    else if (name.startsWith("on")) node[name.toLowerCase()] = value;
    else if (["classNames", "textContent"].has(name)) node[name] = value;
    else node.setAttribute(name, value);
  }

isTextNode

This option is used internally to determine if this node is a text node or not.

isTextNode(node) {
    return node.type === 3;
}

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.

getParentNode(node) {
    return node.parentNode;
}

getFirstChild

This option is being used internally in SolidJS renderer to get the first node of a given parent node.

getFirstChild(node) {
    return node.firstChild;
}

getNextSibling

This option is being used internally in SolidJS renderer to get the next node of a given node in the DOM tree.

getNextSibling(node) {
    return node.nextSibling;
}

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.

import renderer from "./renderer";

renderer.render(() => <App />, document.getElementById("root"));

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.

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

Introducing the New SolidJS and Tailwind CSS Starter Kit cover image

Introducing the New SolidJS and Tailwind CSS Starter Kit

We are delighted to announce our SolidJS + Tailwind Starter Kit on Starter.dev to help you build your next project with SolidJS and Tailwind CSS in no time. In this article, we will walk you through the kit, and how it is a great option for building your next project. This kit has been designed to build tiny and optimized web applications on both the JavaScript and CSS sides. It's very useful when the load time of your application's files is critical, like when the internet connection is slow or when you are building a mobile application for a targeted audience. Why SolidJS? SolidJS is a declarative, efficient, and flexible JavaScript library for building user interfaces. It lets you build web applications using a declarative API that you’re already familiar with. It’s a great alternative to React, Vue, and other popular JavaScript frameworks. It’s also a great choice for building static sites. The best feature of SolidJS is how tiny its build bundles are. It helps you ship less JavaScript to the browser. Why Tailwind CSS? Tailwind CSS is a utility-first CSS framework for rapidly building custom user interfaces. It makes it easy to build complex UIs without having to write custom CSS. And the best feature of Tailwind CSS is how customizable it is. It helps you ship a very small CSS file to the browser. --- Starter Kit Features - SolidJS - A declarative, efficient, and flexible JavaScript library for building user interfaces. - Tailwind CSS - A utility-first CSS framework for rapidly building custom user interfaces. - Vite - A build tool that aims to provide a faster and leaner development experience for modern web projects. - Storybook - An open-source tool for developing UI components in isolation for React, Vue, and Angular. - Vitest - A fast and simple test runner for Vite. - TypeScript - A typed superset of JavaScript that compiles to plain JavaScript. Tailwind CSS Tailwind CSS makes it easy to build complex UIs without having to write custom CSS. The best feature of Tailwind CSS is how customizable it is. It helps you ship a very small CSS file to the browser. Also, Tailwind CSS is great at scale, so you can add as many utilities as you want. Storybook Storybook is an open-source tool for developing UI components in isolation for React, Vue, Angular, and others. It makes building stunning UIs organized and efficient. It also helps you build components faster, and reuse them in your projects. It also helps you document your components. This kit comes with Storybook pre-configured. You can start using it right away. Why Vite? Vite is a build tool that aims to provide a faster and leaner development experience for modern web projects. It's a great alternative to Webpack. It's fast and easy to use. It also helps you ship less JavaScript to the browser. It's a great choice for building static sites. This kit comes with Vite pre-configured. You can start using it right away. Testing This kit comes with Vitest pre-configured. Vitest is a tool that is built with Vite in mind from the start, taking advantage of its improvements in DX, like its instant Hot Module Reload (HMR). This is Vitest, a blazing fast unit-test framework powered by Vite. It's a great alternative to Jest. It's fast and easy to use. How to get started? - Run npx @this-dot/create-starter to start a new project from one of the kits in the Starter CLI library. - Select the SolidJS, Tailwind kit from the CLI library options - Name your project - cd into your project directory and install dependencies using the tool of your choice (npm, yarn or pnpm) - Copy the contents of the .env.example file into a .env file When to use this kit? This kit is a great alternative to React and SCSS. It focuses on performance and developer experience by providing a fast and leaner development experience for modern web projects. It also helps you write less CSS, and less JavaScript. Options to scale the kit to fit your needs In this section, we will walk you through the options you have to scale the kit even further to fit your needs. We didn't want to add too many options to the kit in order to keep it simple and easy to use, but we also wanted to provide you with the ability to scale the kit. PWA PWA is a great way to make your app available offline, and installable on mobile devices. It caches your app's assets to make it load faster. It also helps you build a great user experience, and increase your app's engagement by providing push notifications. If you want to add PWA support to the kit, you can use the PWA Vite Plugin to add PWA support to the kit. It covers the PWA integrations for Vite, and the ecosystem with zero-configuration and is framework-agnostic. Conclusion So as we discussed in this article this SolidJS starter kit is a great way to start your new SolidJS project because it has most of the tools you need installed and preconfigured for you from Storybook to Testing, and even the PWA configurations. We hope you enjoyed this article, and we hope you will enjoy using the kit. If you have any questions or suggestions, please feel free to reach out to us on Twitter or Contact form....

Understanding Effects In SolidJS cover image

Understanding Effects In SolidJS

Understanding Effects In SolidJS In SolidJS, effects are a fundamental concept that helps developers manage side effects and reactive dependencies within their applications. Unlike standard functions that execute once and are done, effects in SolidJS are designed to automatically re-run whenever their dependencies change. This article will explore what effects are, how to use them, manage dependencies, handle multiple signals, and the lifecycle functions that enhance their functionality. What Are Effects? Effects in SolidJS are functions that automatically execute when the signals or reactive values they depend on change. This capability makes effects essential for managing side effects like DOM manipulations, data fetching, and subscriptions. Creating an Effect To create an effect in SolidJS, you use the createEffect function. This function takes a callback that runs whenever the effect is triggered by a change in its dependencies. ` In this example, the effect logs the user’s online status to the console. Each time the isOnline signal changes, the effect re-runs and logs the updated status. Managing Dependencies Dependencies in effects are the reactive values or signals that an effect observes. When any of these dependencies change, the effect re-runs. Interestingly, SolidJS automatically tracks these dependencies, meaning you don’t need to manually specify them, which reduces the risk of errors. When an effect is initialized, it runs once even if its dependencies haven't changed. This initial run is useful for setting up the effect, initializing variables, or subscribing to signals. ` Subscribing to Signals When an effect observes a signal, it essentially subscribes to it. This subscription allows the effect to re-run whenever the signal's value changes. ` In this example, the effect logs the current temperature whenever it changes. Managing Multiple Signals Effects in SolidJS can observe multiple signals simultaneously. This means that a single effect can track changes in multiple reactive values, re-running whenever any of them change. ` Here, the effect monitors both temperature and humidity signals. It re-runs when either signal changes, ensuring that it always logs the latest values. Nested Effects SolidJS allows effects to be nested within each other. Each nested effect independently tracks its own dependencies, ensuring that changes in an inner effect don’t inadvertently trigger an outer effect. ` In this example, changes to dependencies in the inner effect will not affect the outer effect. This separation prevents unintended behaviors and keeps effects isolated from one another. Lifecycle Functions SolidJS offers lifecycle functions that give you more control over when effects are initialized and disposed of. This can include running a side effect only once, or cleaning up a task when it is no longer needed. onMount The onMount function is used when you need to run a side effect only once, typically when a component is initialized. Unlike effects, which can re-run multiple times, onMount ensures that the callback is executed only once. ` This function is perfect for tasks like fetching data or setting up subscriptions that only need to happen once when the component mounts. onCleanup The onCleanup function is used to clean up tasks when a component is unmounted. This is particularly useful for clearing intervals, removing event listeners, or unsubscribing from services, thereby preventing memory leaks. ` In this example, onCleanup ensures that the interval is cleared when the component is unmounted, preventing it from running indefinitely in the background. Understanding the Execution Model of Effects in SolidJS SolidJS introduces a fine-grained reactivity) system that sets it apart in the UI development landscape. Unlike React, where effects are tied to component lifecycles and can sometimes trigger unnecessary re-renders, SolidJS operates at a much more granular level. It tracks dependencies down to individual signals or computations, enabling highly efficient updates. When you create an effect using createEffect, SolidJS automatically monitors every reactive signal accessed within that effect. It builds a precise dependency graph, which maps out exactly which effects should be re-executed when specific signals change. This approach ensures that only the necessary parts of your application update in response to state changes, resulting in more efficient rendering and overall better performance. Conclusion Effects in SolidJS are a powerful feature that enable you to react to changes in your application's state dynamically. By leveraging createEffect and using lifecycle functions like onMount and onCleanup, you can create robust and responsive applications. Understanding how to effectively use effects will help you build more efficient, maintainable, and bug-free SolidJS applications....

Lessons from the DOGE Website Hack: How to Secure Your Next.js Website cover image

Lessons from the DOGE Website Hack: How to Secure Your Next.js Website

Lessons from the DOGE Website Hack: How to Secure Your Next.js Website The Department of Government Efficiency (DOGE) launched a new website, doge.gov. Within days, it was defaced with messages from hackers. The culprit? A misconfigured database was left open, letting anyone edit content. Reports suggest the site was built on Cloudflare Pages, possibly with a Next.js frontend pulling data dynamically. While we don’t have the tech stack confirmed, we are confident that Next.js was used from early reporting around the website. Let’s dive into what went wrong—and how you can secure your own Next.js projects. What Happened to DOGE.gov? The hack was a classic case of security 101 gone wrong. The database—likely hosted in the cloud—was accessible without authentication. No passwords, no API keys, no nothing. Hackers simply connected to it and started scribbling their graffiti. Hosted on Cloudflare Pages (not government servers), the site might have been rushed, skipping critical security checks. For a .gov domain, this is surprising—but it’s a reminder that even big names can miss best practices. It’s easy to imagine how this happened: an unsecured server action is being used on the client side, a serverless function or API route fetching data from an unsecured database, no middleware enforcing access control, and a deployment that didn’t double-check cloud configs. Let’s break down how to avoid this in your own Next.js app. Securing Your Next.js Website: 5 Key Steps Next.js is a powerhouse for building fast, scalable websites, but its flexibility means you’re responsible for locking the doors. Here’s how to keep your site safe. 1. Double-check your Server Actions If Next.js 13 or later was used, Server Actions might’ve been part of the mix—think form submissions or dynamic updates straight from the frontend. These are slick for handling server-side logic without a separate API, but they’re a security risk if not handled right. An unsecured Server Action could’ve been how hackers slipped into the database. Why? Next.js generates a public endpoint for each Server Action. If these Server Actions lack proper authentication and authorization measures, they become vulnerable to unauthorized data access. Example: * Restrict Access: Always validate the user’s session or token before executing sensitive operations. * Limit Scope: Only allow Server Actions to perform specific, safe tasks—don’t let them run wild with full database access. * Don’t use server action on the client side without authorization and authentication checks 2. Lock Down Your Database Access Another incident happened in 2020. A hacker used an automated script to scan for misconfigured MongoDB databases, wiping the content of 23 thousand databases that have been left wide open, and leaving a ransom note behind asking for money. So whether you’re using MongoDB, PostgreSQL, or Cloudflare’s D1, never leave it publicly accessible. Here’s what to do: * Set Authentication: Always require credentials (username/password or API keys) to connect. Store these in environment variables (e.g., .env.local for Next.js) and access them via process.env. * Whitelist IPs: If your database is cloud-hosted, restrict access to your Next.js app’s server or Vercel deployment IP range. * Use VPCs: For extra security, put your database in a Virtual Private Cloud (VPC) so it’s not even exposed to the public internet. If you are using Vercel, you can create private connections between Vercel Functions and your backend cloud, like databases or other private infrastructure, using Vercel Secure Compute Example: In a Next.js API route (/app/api/data.js): ` > Tip: Don’t hardcode MONGO_URI—keep it in .env and add .env to .gitignore. 3. Secure Your API Routes Next.js API routes are awesome for server-side logic, but they’re a potential entry point if left unchecked. The site might’ve had an API endpoint feeding its database updates without protection. * Add Authentication: Use a library like next-auth or JSON Web Tokens (JWT) to secure routes. * Rate Limit: Prevent abuse with something like rate-limiter-flexible. Example: ` 4. Double-Check Your Cloud Config A misconfigured cloud setup may have exposed the database. If you’re deploying on Vercel, Netlify, or Cloudflare: * Environment Variables: Store secrets in your hosting platform’s dashboard, not in code. * Serverless Functions: Ensure they’re not leaking sensitive data in responses. Log errors, not secrets. * Access Controls: Verify your database firewall rules only allow connections from your app. 5. Sanitize and Validate Inputs Hackers love injecting junk into forms or APIs. If your app lets users submit data (e.g., feedback forms), unvalidated inputs could’ve been a vector. In Next.js: * Sanitize: Use libraries like sanitize-html for user inputs. * Validate: Check data types and lengths before hitting your database. Example: ` Summary The DOGE website hack serves as a reminder of the ever-present need for robust security measures in web development. By following the outlined steps–double-checking Server Actions, locking down database access, securing API routes, verifying cloud configurations, and sanitizing/validating inputs–you can enhance the security posture of your Next.js applications and protect them from potential threats. Remember, a proactive approach to security is always the best defense....

This Dot AI Field Notes - Anatomy of a Coding Harness cover image

This Dot AI Field Notes - Anatomy of a Coding Harness

A coding agent is not magic, it’s a loop. We call this a harness. The harness is a deterministic layer of code that wraps an LLM. Claude Code is a harness. Codex is a harness. Pi is a harness. The harness, on initialization, provides to the LLM a system prompt defining all tools the harness implements for the LLM. Without the harness, you cannot read or modify files on the user’s local filesystem without them having to copy-and-pasting by hand. The harness is the final place where engineers can customize how coding agents do work before the LLM takes over. Think of the LLM as a train and the harness as the rails the train rides on. Below… one full task executed by a harness, traced step by step....

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