How to avoid caching small user-specific components when using SSR

My goal is to build each page of a somewhat traditional ecommerce storefront experience top-to-bottom in Builder.io, with server-side rendering by Lambda@Edge, and cached by CloudFront. This seems straightforward for the most part, but what’s not clear is the best practices surrounding the component(s) on each page that must be rendered client-side so we don’t cache user data and serve it up to someone else.

Every page of most ecommerce stores has some sort of header block that shows your name/account (or just “Sign in”), as well as how many items are in your cart. If these items can be marked to render client-side only, that means that each page of the app, with exception of obvious account/cart/checkout pages, can be cached and served up for every request, regardless of the user making the request. Those portions will update as soon as the page loads in their browser.

What are the best practices for achieving this with Builder?

Hi @Tom360, I haven’t specifically used Lambda@Edge, but I can give my thoughts.

Say you have a header designed as a section within Builder. The header has a text block showing the user’s name populated with a dynamic data binding. Maybe that’s the short answer you’re looking for and you can stop reading now :slight_smile:.

The data binding is just a bit of JS code, something like state.fullName. At least when using the React SDK, the one I’m most familiar with, the binding will execute twice (not including re-renders on the browser): once on your back end during SSR and once in the browser during hydration. The SDK is what executes the binding code, spits out a result, and associates it with your text block’s value.

So in this case, which is a pretty typical use case, the binding itself is what’s part of your header’s content and that’s what gets fetched by your server from Builder, not the end result. CloudFront would end up caching the binding JS along with the rest of your header section’s content (info about layout, which blocks are in the header, styling, etc.) But the code executes per render since it’s executed by the SDK.

A separate but related question is “how does state.fullName get populated?” Well, the state variable in Builder is a complicated beast with many potential inputs, but in this case, typically you would pass your state into the SDK during render. If you’re using React, it would be <BuilderComponent data={...} />, where data is the prop that populates state. Then at render time, the SDK executes the data binding with the state you’ve just populated within the execution context (during SSR, the execution context is a Node VM, and during CSR it’s a closure with state in scope).

The point is, state isn’t getting cached, either–at least, not if you populate it using the data prop. There are ways to pre-populate state as part of the section content, in which case that would get cached, but you’d have to go out of your way to do it. At any rate, anything that you pass into the data prop has precedence when it comes to populating state.

Hope that helps.

Hi ersin, thank you so much for taking the time to write that wildly insightful post! I really appreciate it!

I had just spent a couple evenings digging into the weeds of Qwik and the incredible innovation there that I guess I was stuck in that mindset without really communicating that’s where my head was. Since Qwik doesn’t hydrate and doesn’t have any javascript that runs on the browser’s load events, that told me that Builder’s going to need to prerender everything the user sees server-side.

Since my post and looking into Qwik even further, I see there’s a useClientEffect hook that can execute code client-side as soon as the component is visible. But since Builder doesn’t support custom components in Qwik yet, I’m not sure that’s useful for this case.

Do you know if there’s under-the-hood magic that makes your post also apply to using the Qwik API? And if not (or, actually … even if so!) is there a timeline you might be able to share about when Builder might launch support for custom components on Qwik?

Thanks again!

Hey @Tom360 just wanted to jump in here, appreciate this great discussion!

For Qwik, there are two options you have at builder, either using the Qwik API, which it sounds like you have explored, or the actual Qwik framework itself. With the Qwik framework, we have a full SDK just like many of our other frameworks, and that supports custom components out of the box.

The QWIK API, however, takes your inputs and parameters and then generates some HTML, JS, CSS etc using Qwik on Builder’s servers before sending the output as an API response. Because the HTML and any logic associated is generated on our servers, there isn’t really a way to include custom components, as our servers would never have access to the context of your app to really understand what a given custom component is/should look like.

So because of that, the QWIK API won’t ever really support custom components. But it is very powerful for generic builder-components! Meanwhile, the Qwik framework and SDK already does support custom components, and so might be worth exploring for you!

Hope this distinction helped clarify a few things, as always let me know if you still have more questions!

Incredible, thanks Tim! Somehow I missed the Qwik SDK in all that reading. That’s really exciting.

One thing that’s not immediately clear from that Readme: How would I make a Qwik component available to the Builder UI? To use the above example, let’s say I did create a component to say “Sign in” or “Welcome, TimG!” depending on whether you’re currently logged in, that uses useClientEffect to populate client-side. How can I make that component available for my marketing team to drag and drop on a Builder UI?

Thanks for all your help!

hey @Tom360 we are in the process of building out our Qwik / Builder SDK docs, but the code for adding custom components in our Qwik SDK is the same as our other Mitosis-generated starters, which can be found here: builder/packages/sdks/output at main · BuilderIO/builder · GitHub

A quick example of a custom component based on the Qwik starter outlined here could look something like this:

import { component$, Resource, useResource$ } from "@builder.io/qwik";
import { useLocation } from "@builder.io/qwik-city";
import { getContent, RenderContent, getBuilderSearchParams } from "@builder.io/sdk-qwik";

export const CustomComponent = component$((props) => {

  return (
    <div>{props?.text}</div>
  );
});

export const BUILDER_PUBLIC_API_KEY = "YOUR_API_KEY"; // ggignore
export const BUILDER_MODEL = "page";
export const CUSTOM_COMPONENTS = [
  {
    component: CustomComponent,
    name: 'Custom Component',
    inputs: [{name: 'text', type: 'string'}]
  }
]

export default component$(() => {
  const location = useLocation();
  const builderContentRsrc = useResource$<any>(() => {
    return getContent({
      model: BUILDER_MODEL,
      apiKey: BUILDER_PUBLIC_API_KEY,
      options: getBuilderSearchParams(location.query),
      userAttributes: {
        urlPath: location.pathname || "/",
      },
    });
  });
  
  

  return (
    <Resource
      value={builderContentRsrc}
      onPending={() => <div>Loading...</div>}
      onResolved={(content) => (
        <RenderContent
          model={BUILDER_MODEL}
          content={content}
          apiKey={BUILDER_PUBLIC_API_KEY}
          customComponents={CUSTOM_COMPONENTS}
        />
      )}
    />
  );
});

This is a simple component that takes a text input and shows it on the page, but the custom component could have any logic or functionality you have in mind. As long as it is registered and then passed into your <RenderContent /> component, you will have access to it inside of the Builder Visual Editor.

Hope this helps!

Brilliant, that’s exactly what I’m looking for! Thank you @TimG! I really appreciate the help :slight_smile:

1 Like

Glad to help! Let us know if you have any other questions!