How JavaScript Rendering Powers Business-enabled Sites | HubSpot.Extend() 2022

HubSpot engineers Byron Matto, Tim Finley, Ben Brings, and Charles Balma show how they're modernizing the CMS with server-side React components, islands for progressive hydration, and new local development tools. The goal is faster, more flexible developer workflows.
Foreign.
Hello everyone. My name is Tim Finley, my pronouns are he, him, and I'm a principal Engineer working on the HubSpot content platform. For for this CMS keynote, we are pumped to talk about the steps we are taking to modernize your experience developing on the HubSpot CMS, and I'm going to be joined by my fantastic colleagues Byron, Ben and Charles to give you a preview and some sweet demos of how you'll be able to develop on the HubSpot CMS in the near future. However, I want to start by taking a minute to look back. The web has come a long way in the past 10 years. We've seen the rise of component based frameworks like React, the rise of design systems, JavaScript evolving with ESM TypeScript, an explosion of web APIs, a myriad of bundlers, new performance metrics, etc. But how much has the core of how you code on the HubSpot CMS changed during that time? Many of my teammates would rightfully point out improvements to our like our local CLI evolution of custom modules, themes and GraphQL and GraphQL by the way, if you didn't already know, is out of beta and available to all pro accounts, please use it up. However, despite those improvements at a fundamental level, we're still a template language driven platform that stands out more and more starkly from the rest of the ecosystem. And we've heard from you. We've heard from those of you that want better tooling, those of you that want more flexibility, more control, more power, and those of you that wish it was easier to get new developers up to speed on HubSpot quickly, and those of you that want to get more of the nice and shiny things you see and use everywhere else. So I and we think it's time to start changing that. Today we're going to be giving you an early preview of a more modern HubSpot CMS developer experience where you where you can code server side React alongside your Hubble templates. You can use islands to progressively hydrate interactive components where you can preview local component changes and real pages. Much more. To be clear, Hubble is not going away anytime soon. Not only do you and our customers have hundreds of thousands of existing pages that need to keep working, but we believe it's important to make platform shifts incrementally and with you your feedback. So let's go over the agenda. I'm going to be with you for this introduction and overview. Then we're going to go through a demo of some building blocks with Byron Ben is going to walk you through the benefits of Islands and give another demo, and Charles is going to close us out and look forward. So, enough high level context, let's get into some details. So today we're going to preview these four things. We're going to preview JavaScript partials, JavaScript modules, islands, and the HubSpot CMS dev server. And before we get to those demos, I want to wet your Appetite with some JavaScript partial and module basics. So JavaScript partial is just like the Hubble partials we have today, except that it's implemented as a React component that produces HTML instead of Hubble. On the left here we have a superficial example of a Hubble partial. There's an include tag from a base template that includes hubble from sidebar HTML. And so let's compare that to a JSPartial. Looking at the example code on the right, notice how there's a jspartial tag instead of an include tag. And those of you that are familiar with React will notice that the contents of the JS partial in sidebar JSX looks just like a regular old React component. Similarly, with JavaScript modules, instead of using the module Hubble tag that refers to a module folder with HTML, JavaScript, JSON, and CSS, you're going to reference them with the JSModule hubble tag. And that's going to refer to an index JS file that's inside the module folder and that's going to export three things. Number one, module fields defined through jsx, some meta information, and three, a React component that does the module rendering. I really want to emphasize how you're going to have the full power of JavaScript inside this module definition code. You can import from other files, you can generate fields programmatically, and you can reuse other React components and so much more. Okay, I realize that we already covered quite a bit here, but don't worry, we're going to get to many more details in our demos shortly, and there's an important point that I want to underscore before I leave you today, and that's that when we release these new primitives, you're going to be able to use them in your existing templates and your existing themes, so you can incrementally try them out and use them where they make sense without having to make a complete technology switch. Under the covers, the output of server rendering your React components will get stitched alongside the Hubble template output. Looking at this diagram, you can see the JavaScript building blocks inside of a Hubble template that are highlighted in orange, and the output of those two JS components is directly in line with the rest of the server HTML response. So while your code is in React and and in Hubble, it all looks like one thing to the browser and your visitor. Also, just like today, your JavaScript building blocks can be statically pre rendered and cached unless you pass dynamic data or dynamic request parameters into them. Even further, these server rendered React components will not add any client side JavaScript to your page unless you want them to. That means that you can get the benefits of writing React component code like composability, access to NPM packages, the wealth of knowledge on Stack overflow, et cetera, without having to pay the cost of render blocking client side JavaScript. And when you do need interactivity, you can use islands to sprinkle in components that get rendered on the server and browser precisely when and where you want them to. Pretty cool, huh? So, without further ado, I'd like to introduce you to Byron, who's going to give you a demo of these new building blocks and the developer experience we're previewing today. Take it away Byron.
Thank you Tim, and greetings to everyone taking the time to help us extend the HubSpot ecosystem. I'm so excited to share with you this demo of the JS building blocks. My name is Byron Matto. I use he him pronouns. I am an engineering lead who has been working here at HubSpot for four years, focused entirely on the CMS. I feel so lucky to have the opportunity to work on the developer experience. As a developer myself, being able to work toward a better quality of life for those building on the CMS has special meaning for me. But enough talk. Let's look at some real examples of JS building blocks. So this is a web page on the HubSpot CMS. No big deal. There are many CMS pages like 800,000 of them. The exciting thing about this page however is is that 99% of the HTML was written in JSX and rendered with React. Now React apps are nothing new, and maybe this is a small HTML file with a client side React app included, but in fact this page is 100% server rendered React. Let's take a look at some code. Now we're going to start with Hubble because the way a developer will introduce the server rendered React is through the JS building blocks in Hubble. Here's the base Hubble template and as you can see we're using the new JS partial to include a header component here and a footer component here. And then we have a home Hubble template which extends base and it's also using partial to point to home jsx and a JS partial. From the Hubble perspective, works kind of like an include works. Today you can expect the HTML output of the associated jsx to render inline where the partial has been placed. So looking at our home jsx file, I want to point out a couple of things. First is that we're importing a CSS module. If you aren't familiar with CSS modules, it's not a problem. What's important is that because we are using vite under the hood for various buildings builds and tooling, we are getting for free rollupjs bundler. There are a host of different approaches to dealing with CSS and what the developer is getting here is more control over over which approach they take. The developer will be able to create a postcss config file which they can then modify to use things like auto prefixer, source maps, different parsers, et cetera. In this case, we're using that to accomplish the importing in jsx of a CSS module. We'll automatically bundle the process CSS with your code and ensure its delivery via cdn. You don't need to write link tags or configure page settings. Super simple, super powerful. Maybe you're not into CSS modules. That's cool too. We want to invest in allowing our developers the flexibility to use the technologies they prefer. Something else is worth mentioning is I'm importing a couple of other JSX components. Layout and HubSpot Sprocket. Now, layout is just a wrapper with some styles, and HubSpot Sprocket was that spinning logo you saw earlier. One of the big wins we think developers will love is greater code reuse and composability that comes with having access to the JavaScript language and JSX templating. So here's my sprocket component and you can see that I'm using a simple react UI library called Reach to help with some accessibility. We want developers to be able to install and use third party dependencies. The JavaScript package ecosystem is truly massive and developers need to be able to make use of this community. Okay, let's take a deep breath. There's a lot of things to talk about here and directions to go in, but I think what will be helpful is if we hit the on switch now. Today, the developer's experience essentially is completely tied to either working directly in the design manager or working locally and syncing files to the developable file system. There is no good way to test changes locally before sending them up. Well, today I'm going to show you all project we have been calling the CMS dev server. So I'm going to run a command locally. All right, so I'm up and running at localhost 3000. So we head over here. Okay, so what I want to show you is that the local dev server has created some routes for us by the convention of which JS of what JSX files are in the partials and modules directories in our source code we have those links to those discrete render output of those building blocks. This is useful when developing a small partial or module that you want to iterate on and move quickly. So just a quick look at the directory structure. You can see. So CMS Assets is essentially my source directory and in there I have a components directory within which I have partials and modules. Within partials I have these four components within modules I have this to do list module and if we check back here to our dev server, you can see those routes have been created for us. So checking out the to do list module. One great thing about this view is that it supports hot reloading and so if I want to make some changes. So for example, if I want to change some styles like the button, I want to make it yellow, check back and not a great look but get instant feedback, which is great. I think orange is probably better. Ah yes, much better. Working on the individual building blocks can be very useful. However, we also want developers to be able to work on actual CMS pages locally. Let's see what that looks like. Great. Okay, so this route is essentially the same as my live page, except that I've added in localhost3000 into the domain and here we see the hello world homepage rendered locally. This works by proxying current world Hubble renders to a local server which then stitches in and renders react components within this page. Anything made with the JS building blocks can be previewed locally. So if I make a change to the header, so within header, if I change this to hello and refresh my local dev server, great. I see those changes locally. So I've made these changes locally and I'm happy with them and I want to push them live. Now we have built on top of the developer projects infrastructure projects serves HubSpot generally as a mechanism for building and deploying their code. Developers. Yeah, for building and deploying their code out. I'M going to use the HubSpot CLI to upload my JS building block code. So in here HS run. Okay, so as you can see, the build is kicking off and what's happening here is that our JS building block code is being built and combined with our JS rendering logic. Once that build is complete, it will be deployed and the compiled assets will be live on our infrastructure. And it's done. Now, because we have built on projects, we can look to the project's UI for some output. So note our build number is 42, which is the answer to everything. Okay, so back to projects. Our latest build is 42. It's great. And as you can see, our build output here is in the project's ui. This can be helpful when debugging a failed build. For example, let's check out our live page. All right, you can see our changes. Hopefully this has illustrated well some of the building blocks, how they are used and how to leverage the local dev server. And lastly, our projects and build deploy. All right, so at this point we've walked through JS partials, CSS modules and bundler benefits, JSX composability and reusability, including third party dependencies, the CMS dev server, local page proxying, and build and deploy with projects. Now I want to step back into our hello World code base to examine some more specific features. So back to our Hubble template. What I want to do is pass some information from Hubble to my header JSX partial. So I want to pass in this header titled parameter looks good. So now that I'm passing that into the JS partial in the header jsx file, I can access that via props. So if I say props header title called that. Okay, if I check that render locally, there it is. Awesome. So that's just showing that we can arbitrarily pass any number of parameters to the JS partial and then access them via props. In our JSX component, as is being announced, GraphQL is moving out of public beta and will be generally available. In the following example I will show how we can apply what we just learned. In a scenario we are querying data using graphql. So I have a custom object in my portal called cars and I have it because I like to keep track of what cars my teammates like, because knowing what kind of transport your teammates prefer is very illuminating. So this GraphQL file has got a query in it which is querying my cars collection and then using the associations to get the contact associated with Them this query, I built it in the graphical interface that's in the HubSpot app. It's very, very useful tool. So if I take a look at my cars Hubble. There we go. You can see that I'm making use of that GraphQL query with this data query path template annotation and then I'm passing it into JS partial as car collection here. Okay, so now if I look at my cars JSX file here, you can see. So here comes car collection. Yep. So I'm passing that out as cars and I'm mapping over that and creating the HTML and let's see what that looks like on cars. Oh nice. Now I can easily associate my teammates with the transport option. Again, critical information to have. Now currently this is the only way we can get data into a JS partial by passing along data as params in the Hubble tag. In the future, we will be building in special APIs for developers to get data fetching done within the react code itself. Now, hopefully it's starting to become clear how these building blocks can open the door to even more expressive power within the HubSpot CMS. But there's one more thing I want to talk about. Modules. Modules are a key building block for the cms, especially when it comes to allowing the marketers to have control over various areas of content. As developers who have worked with modules in the HubSpot CMS know, defining module fields in fields JSON can be an arduous and repetitive process. With JS building blocks, we are introducing the ability to write your modules in jsx. As you can see, we will be defining all of the existing HubSpot module fields types as JSX components and publishing them publicly. The developer need only import those field types and components like field group to define their modules fields. Of course, a JSX is a superset of JavaScript and so developers will be able to programmatically create these field definitions. This has very meaningful implications for read code reuse composability and developer efficiency. We think developers will love describing their module fields in this format. A module is more than just fields, of course. It has a template and meta information. As Tim mentioned earlier, a module index file need only export these module parts. So as you can see, I'm exporting my meta, exporting my component and exporting fields here for an index jsx file inside of my to do list module. How do you access the field values you ask? Field modules from your module will be passed into the module react component as Props we think this is an elegant and intuitive way for developers to access the content they want to expose. So this to do module we have been looking at has something else special going on. It is taking advantage of a new special building block that can be used inside of JSX within our stacks. This building block is called an island. Islands may be familiar to you if you've taken a look at the Astro or Dento fresh frameworks. We have no shame in taking what we think are some of the best ideas from the larger web community. Do a quick demo of the To Do App module so this little to do app is essentially a micro react app. It is making use of React Hooks API to manage state. So it's got client side interactivity. So I can do now. Great. It's making use of the React Hooks API to manage state, it's adding event handlers and of course doing an efficient job of managing the render state of the HTML. The most important takeaway here is that I haven't added any JSS via Template includes or any other page settings. Simply the usage of the Islands API was all I needed to ensure the page had all the client side code needed to run my little to do app within a JS module building block. Plus, since these to do components are contained inside a module, that means that others using HubSpot page editor can customize its field without having to know or touch the code. So your non technical clients and co workers won't see any difference between a JS module and a Hubble module. They will all work the same in the HubSpot page editor. To talk more about Islands in detail, I'm going to pass this off to my teammate Ben Brings.
Thanks Byron. Hey everyone, my name is Ben. I'm here to talk to you a little bit about Islands in the context of our JavaScript building blocks and how they can help you more efficiently build performant interactivity into your CMS pages. So when we talk about interactivity in the cms, what exactly do we mean? There's a few kind of categories that we might use to describe a lot of the use cases for this. So something relatively limited in size as part of a larger static page, like an image carousel or something a little more complicated. Still not the entire page, like a form with some sort of validation or something that's less of a page and more of a full application. So something like the To Do List app that Byron showed earlier in the demo. And we want to support all these kind of use cases within the cms. So since Hubble only runs on the server. We need some sort of other client side JavaScript to achieve any of these use cases within the CMS. How do we do that today within the CMS, one approach might be to skip Hubble entirely for any sort of content and not only handle interactivity, but all rendering of content with client side JavaScript. The big downside of this approach is that you're going to have a lot that the browser needs to do before you render any content to your users, and doing it on the client side means that you're going to have poor performance and a not as great user experience. Additionally, you'll need to as your JavaScript scales out in size with as your page grows in complexity, you might need to use a bundling solution or a rendering library or both as part of your infrastructure, and that requires a decision and work to set all that up yourself. Additionally, because your initial server side render is empty of any content, this can negatively affect your SEO if crawlers decide to not check what any sort of JavaScript rendered content is before indexing your page. Another approach would be using Hubble for your initial render of content and then having JavaScript that just attaches and manages interactivity on top of that. The big downside here is that you're going to now have two code paths in two different languages that you have to keep synced up over time. So if you want to make a change, you'll need to make it across both Hubble and JavaScript and make sure that those are in lockstep and not in conflict in any other way or out of sync. And you still might need a bunch of the bundling or rendering infrastructure that comes with the all client side JavaScript approach. As your page gets bigger and if you're not doing any sort of bundling, you might need to use between different JavaScript files, the window to reference code and dependencies between them, which can also be a pain to maintain. So what exactly are islands? Islands are giving developers the precise control of loading client side react components to build interactivity into your page. So specifically, after initial server side render of HTML using Hubble and our JavaScript building blocks, an island will load a JavaScript bundle on the client side, attach it to the DOM which already has content and is ready to respond to user events like a usual client side react component. So in this diagram here we have an initial server side render that is uninteractive of the image gallery. We then load just the gallery component on the client side and then hydrate it onto the page so that it is now interactive and ready to respond to any sort of clicks or user events. And we can also have multiple islands on a page. So in this case we have multiple instances of the same island, but we also could have multiple islands of completely different code all on the same page, loaded in parallel. So what are the problems we described before do islands solve exactly? Islands are enforcing this concept of hybrid rendering. So having a single code path, in this case a react tree that is rendering both on the client and the server. And this has existed in traditional server side rendering contexts. However, usually you have to render the entire react tree on both the server and the client and for something like an image gallery where you have very limited interactivity in a bigger page, that can be a lot of overhead for the client side for something that should be much more minimal. And so islands let you pick exactly what JavaScript you need to render on the client to load. Islands also give you bundling and code splitting for free. Our platform will do all that for you when you identify what you want to load as an island. And it also keeps the SEO benefits of server rendering because your initial render has all the content, just no interactivity that doesn't matter to a crawler. So it will be able to fully index the page with what you have on it. So how exactly do you use islands? Here we have an example page of a To do list module from the demo that Byron had earlier, and we have a couple components rendered in it. We have the layout component which will be static and we don't want to render on the client. And we also have an interactive to do list component that we do want to render on the client, pass some data to and have respond to clicks and input. So here is the code adjusted to to have an island and there's a couple pieces I want to highlight most of what changed. The first is that we are adding this question mark island parameter to the end of the import of our interactive component. And this is telling our platform that this is a component that we want to code split and bundle up for use on the client side. And then we also, rather than directly rendering the to do list island, we want to use the island component and pass the module as a module prop to it. And this tells the platform where exactly in our react tree we want to ren. We want to hydrate this island component on the client side. And then we have a couple other options here. So we have the id, which would be just like a element ID to identify the island component in the DOM and then a Hydrate on prop that. I'll go into a little more detail in a second. So islands empower us to build our pages with progressive hydration in mind. And progressive hydration is the concept of prioritizing and paralyzing your render of your page into small pieces, rather than having a single large thread blocking render which treats every piece of your app equally. So in this example here we have a few pieces of interactivity on the page. We have a menu dropdown that might be most important to a user immediately. We have a CTA button that someone might use, but maybe not right off the bat when using the page. And then we have an image gallery that happens to be below the page fold. So we have a few options for the Hydradon prop that I mentioned that might fit these. So one would be load load the client side bundle as soon as possible on page load. Idle will wait a bit until any critical JavaScript is executed and using the request idle callback API defined by the browser. And then we also have visible which will wait until the element that has been initially rendered on the server is scrolled to and visible on the user's side, which might be never. So if I never scrolled this example page down, we might never need to load the JavaScript to operate the image gallery. So that can be a potential big cost saving for the performance of your page. And so if we were to color code all these options here, we would use load for the menu dropdowns being most important, then idle maybe for the CTA button and visible for the image gallery. So to look see what that looks like in code, we have a two list down here that would load on idle and we have a bunch of static content that could maybe never, that could never make it to the client is only server rendered and then a visible image gallery island here. All right, so let's do a demo real quick just to kind of show exactly what it is like to in your development context add an island to a page. All right, so picking up on Byron's demo earlier, we have a to do list module here that is exactly the same, but we haven't, we don't have any interactivity enabled here. So I can type in this text input but I can't click on anything. We don't have any sort of hover events here. No state the list is happening on the front end here and we can see we only have of the JavaScript load on the page only pieces that are dev server related. We don't have any of our actual application code rendered on the client side. So let's go to the code real quick and we can see we have the same to do list component rendered directly here because we don't have any island set up, we don't have any client side interactivity. So let's do that now. So what? First thing we want to do is add the island parameter here onto the end of the import and then we want to use the import island from the CMS components library. And then rather than rendering directly in the tree here, we want to use the island component and pass the module as the module prop. And then for now let's say we want to hydrate on load. So let's save that and go back to our page and we can see now we have interactivity. Our our state is up and running. I can add a to list here, I can add to do here. And then we can see that we have the To Do List JavaScript component loaded on the front on the client side and hydrating on the page here. So what if we had a bunch of content here in our page. So if I add that we have a bunch of content on our page that moves our interactive component below what might fit on the page or below above the fold and we want to only load it when we scroll down to it. So if we change hydrate on to visible here as we talked about before. So if I refresh the page now, we now see that we don't have any to do list JavaScript load here because we don't see it. But as we scroll down we can see all of our JavaScript files here load in and hydrate only when we view it. So we're hoping that this is a nice way to pick and choose and let you pick and choose exactly what matters to your users and load it in times that makes sense for your users. All right, so hopefully that's the quick introduction to Islands and I'll pass it off to Charles to wrap it up. Thanks so much. Thanks, Ben.
Hi everyone. My name is Charles Balma and I'm a product manager on the HubSpot CMS team. I know we just covered a lot of exciting new features today, so I'd like to take a few minutes to recap and look forward. The key thing to note is that Hubble is not going away. We're expanding beyond our current templating language to provide you with modern, familiar and efficient frameworks like React. In our upcoming beta, you will be able to code server side React alongside Hubble templates. Gain more control over how your pages, hydrate using islands and preview your updates locally before pushing new code to production. The features discussed today are a great start, but we're considering a wide array of potential next steps to expand JavaScript capability within the larger HubSpot ecosystem. All right, let's take a step back from the details for a minute. The JavaScript building blocks that we covered today are one of the many changes that we're making to give you modern frameworks and workflows that fit your needs and preferences. Last year we launched a GraphQL beta, which is a unified solution for interacting with all of your HubSpot data. And today we're announcing projects in the CRM track, which is a code container that gives you a build and deploy pipeline directly integrated into HubSpot. If you're not familiar with GraphQL or React, please use the links on the screen to learn more. All right, so taking another step back, we're working to improve all aspects of the HubSpot developer experience. Beyond modernizing our development frameworks and workflows, we also plan to improve our local developer tooling, documentation, community apps and marketplaces. So with that said, we're excited to invite you to join our JavaScript building blocks private beta. We're still in the early stages of this transformation, and your feedback is key to help us lay the right foundation for the HubSpot development experience. Sign up for the beta using the Beta interest form at the link, on the slides and also in the chat, and that wraps up our presentation today. If you have any questions, there will be a Q and A session later later in the day. Thank you all for the time and please get in touch if you have any additional thoughts that you'd like to share.










