In eCommerce, finding the right balance between performance and personalization can feel like a difficult tradeoff. On the one hand, buyers expect a performant website, and bounce rates increase with every millisecond of latency. On the other, a personalized shopping experience creates higher conversion rates. Personalizing your ecommerce site allows for:
- Displaying offers to users based on their geolocation
- Authenticating requests and gating content
- Showing products or collections to different audiences—for example, showcasing women’s clothes to users who tend to shop in that category
- Showing different content to new vs return visitors
The most common workarounds are to use a hybrid framework that supports both statically and dynamically rendered pages, like Next.js. From there, you can build a primarily static site and fall back to server-side rendering (SSR) for pages that need to be personalized, or to simply allow client-side rendering (CSR) to render personalized page sections (like the sign in/sign out links in a header). Both of these approaches are a lot slower and lead to suboptimal user experience. How do you get the best of both worlds? With Edge Functions.
With Netlify Edge Functions, you can execute arbitrary code at Netlify’s network edge. You create a Next.js middleware capable of geolocation, authentication, audience identification, or any other operation that needs to be executed at request time. Once it’s done, your middleware modifies the request or the response, performing just enough work to personalize statically-generated content.
Edge Functions’ middleware-based approach is much faster than traditional SSR because it doesn’t generate an entire page for each request. It runs in front of static pages or pages served with Incremental Static Regeneration (ISR) by the Netlify CDN, letting you sprinkle some dynamic magic onto your pre-rendered pages, all at the edge, reducing latency.
We’ve provided a tutorial below to give you a taste of how powerful Edge Functions can be, especially when combined with a performant content management system (CMS).
And that’s where Builder.io comes in.
Builder is a visual CMS that enables your teams to build pages quickly and manage content using your existing tech stack and Builder’s drag-and-drop Visual Editor. With additional built-in A/B testing and targeting features, it becomes even easier to create and deliver personalized content to the right audiences.
In this tutorial, you’ll learn how to use Netlify and Builder to:
- Create and deploy a Next.js app onto Netlify.
- Visually create two product collection pages to target two different audiences. For this tutorial, we’ll use clothing shoppers and jewelry shoppers.
- Personalize your statically-generated Next.js pages using Builder and Next.js middleware running on Netlify Edge Functions.
- Implement Builder best practices so that non-developers on your team can easily add to your site and personalize the shopping experience
What you’ll need to create your personalized eCommerce site
If you don’t have them already, create an account with Netlify and one with Builder.
Netlify has a free tier option that has all the features you’ll need to deploy your site.
For Builder, you’ll need to sign up for a free trial, which includes the Growth plan by default. The Growth plan and higher-level plans support custom targeting attributes, a feature we talk about in this tutorial.
Step 1: Create your Next.js App
Create a Next.js app if you don’t already have one started - for instance using this Next.js e-commerce starter. Make sure that you have Node.js installed (as of writing this, at least version 12.22.0) and run the following command:
npx create-next-app@latest
Follow the guided prompts to complete setup. For this tutorial, we’ll use JavaScript rather than TypeScript, and we’ll name our app netlify-builder-edge-demo
.
Once you’ve finished creating your app, change into your new project directory:
cd netlify-builder-edge-demo
Next, install Builder’s dependencies:
npm install @builder.io/react @builder.io/personalization-utils
@builder.io/react
is Builder’s React SDK. With it, you can fetch the pages that you design within Builder’s Visual Editor and render them within your Next.js page templates. @builder.io/personalization-utils
is a lightweight utility library that makes it easier to write the necessary middleware. Both are fully open source.
Install Netlify’s development dependencies:
npm install -D @netlify/build @netlify/functions @netlify/plugin-nextjs
Add the BUILDER_PUBLIC_KEY
environment variable to next.config.js
:
// next.config.js
const nextConfig = {
...,
env: {
BUILDER_PUBLIC_KEY: process.env.BUILDER_PUBLIC_KEY
}
}
Create your Netlify configuration file netlify.toml
:
# netlify.toml
[build]
command = "next build"
base = "."
publish = ".next"
[build.environment]
NEXT_USE_NETLIFY_EDGE = "true"
[[plugins]]
package = "@netlify/plugin-nextjs"
You’re now ready to start your development server with the following command. Replace YOUR_API_KEY
with your Builder account’s Public API Key, which you can find under Account Settings:
BUILDER_API_KEY=YOUR_API_KEY npm run dev
Step 2: Integrate your Builder page model
Before you can create drag-and-drop pages in Builder, you need to integrate Builder with your site.
That’s because when designing a page within the Visual Editor, Builder shows a live preview of your content embedded within your website’s page template along with all of your site’s CSS and any content on that page that’s not managed by Builder. Integration is the magic that makes the live preview work.
To get started, log into your Builder account and click on Models in the left nav. Click Add/Edit Models and then click on the Page model.
On the Model Options page, edit the Preview URL to http://localhost:3000
. Builder’s Visual Editor will now render your content within your Next.js index page while editing.
Click Save when you’re done.
Now let’s write the middleware your app needs to get your personalization data.
Step 3: Create middleware
Next.js supports middleware, which are functions that can arbitrarily rewrite user requests to your site.
Copy and paste the middleware code below into a new file called pages/_middleware.js:
// pages/_middleware.js
import { NextResponse } from "next/server";
import {
PersonalizedURL,
getUserAttributes
} from "@builder.io/personalization-utils";
export default function middleware(request) {
const url = request.nextUrl;
const requestPath = url.pathname;
// Rewrite any URL that has a path that doesn't start with /builder,
// which is special (see below).
if (!requestPath.startsWith("/builder")) {
const query = Object.fromEntries(url.searchParams);
// Create a new URL with a base64 hash in the query params.
const personlizedURL = new PersonalizedURL({
pathname: requestPath,
attributes: {
// Include any builder.userAttributes.* query params.
...getUserAttributes({ ...query }),
// Include the visitor's audience
audience: query.audience || "clothing",
},
});
// Rewrite the URL path with the hash.
url.pathname = personlizedURL.rewritePath();
// After returning the rewritten URL, the browser silently redirects
// to /builder/the-base64-hash, which is our page template from before.
// The URL in the browser's address bar doesn't change.
return NextResponse.rewrite(url);
}
return NextResponse.next();
}
The example above does the following:
- Extracts audience data from query parameters, falling back to
clothing
. You can get this information from any source you want, like cookies, but we’ll stick to query parameters to keep this tutorial simple. - Extracts any
builder.userAttributes.*
query parameters. These are important for Builder editing features like Builder Studio. - Rewrites the request URL to point to
/builder/[hash]
, where[hash]
contains the audience andbuilder.userAttributes.*
query parameters encoded in Base64.
Returning the rewritten URL triggers a silent redirect to the page created previously in this tutorial, which decodes the audience and uses it to target the appropriate Builder page.
From the user’s perspective, the address bar URL doesn’t change and there’s no sign of a redirect. It just works.
Next, you can create the Next.js page template that will fetch and render your personalized Builder page.
Step 4: Create your Next.js page template
Builder’s SDK renders pages that you create in Builder within your framework’s page templates.
Create a Next.js page in your project directory at pages/builder/[hash].js
with the following contents:
import { BuilderComponent, builder } from "@builder.io/react";
import { PersonalizedURL } from "@builder.io/personalization-utils";
import { useEffect } from "react";
builder.init(process.env.BUILDER_API_KEY);
export async function getStaticProps({ params }) {
const personlizedURL = PersonalizedURL.fromHash(params.hash);
const attributes = personlizedURL.attributes;
const page = await builder
.get("page", {
userAttributes: attributes,
})
.promise();
return {
props: {
page: page || null,
attributes,
},
};
}
export default function Path({ page, attributes, locale }) {
useEffect(() => {
builder.setUserAttributes(attributes);
}, []);
return (
{% raw %}
{/* Your header goes here. */}
<YourHeader />
<BuilderComponent
context={{ attributes }}
data={{ attributes, locale }}
model="page"
content={page}
/>
{/* Add the rest of your page here. */}
<RestOfYourPage />
{% endraw %}
);
}
<BuilderComponent>
renders your Builder page, which is fetched by builder.get
within getStaticProps
. This is the basic flow for integrating Builder with any Next.js app.
The example also adds some details for personalizing your page:
const personlizedURL = PersonalizedURL.fromHash(params.hash);
const attributes = personlizedURL.attributes;
const page = await builder
.get("page", {
userAttributes: attributes,
})
.promise();
The page passes the personalization data, received from a query parameter from the middleware created in the next step, to Builder as custom targeting attributes. Builder uses these targeting attributes to pick which page to load.
In order to make targeting work, we’ll set up our custom targeting attributes next.
Step 5: Set up custom targeting attributes
From within your Builder dashboard, click Account Settings in the left nav.
)
Click on the edit button for Custom targeting attributes.
Create a new custom targeting attribute named “audience” with a String type. Click Enum and add two values: “clothing” and “jewelry”, for our clothing shoppers and jewelry shoppers, respectively.
Click Save when you’re done.
You can use your new custom targeting attribute when creating personalized pages.
Step 6: Create two Builder pages
The idea is to use custom targeting attributes to create two pages, with each page personalized to the user based on their shopping preferences. In this case, we’re personalizing content based on whether they shop most often for clothing or jewelry.
To get started, click on Content in the Builder dashboard’s left nav. Once you’re on the Content page, click +New.
When prompted, name your page “Clothing”, set the URL to /
and click Create Page.
Now comes the fun part: creating a clothing page for your store!
Use the Visual Editor to drag and drop image and text blocks onto the editing area to compose your page. Once you’ve got some content up, click the Edit Targeting button.
Within the Targeting modal, click +Target. Add a new Audience target and set “clothing”.
Great! Your page will only appear when you pass “clothing” as a targeting attribute. Click Publish in the upper right corner of the Visual Editor when you’re done.
Now create a page for the jewelry shoppers. Follow the same steps as above for creating your clothing page, and pay close attention to the following details:
- Name the page “Jewelry”.
- As before, set the URL to
/
. - Add an Audience target with a value of “jewelry”.
- Don’t forget to click Publish when you’re done!
Here’s an example of what your jewelry page might look like:
Step 7: Test it locally
Now that you’ve created your personalized pages, it’s time to test it out in your development environment.
Navigate to http://localhost:3000
in your browser. You should see your clothing page:
Why the clothing page? Let’s follow the request:
- An inbound request for
/
reaches your site. There’s no index page for that path, but the middleware intercepts it. - The middleware looks for an audience value from the URL’s search string. Not finding any, it defaults to
clothing
, encodes this data, and silently redirects to/builder/[hash]
where[hash]
is an encoded version of the audience value. - The Next.js page template for
/builder/[hash]
decodes the hash and fetches your Builder page usingclothing
as the value for the audience targeting attribute and/
as the path. - Builder finds two pages for
/
, but only one of them hasclothing
as its audience targeting attribute, which it returns. - Next.js renders the Builder page within the page template and responds with the fully-rendered page.
Now let’s see what happens when we navigate to http://localhost:3000/?audience=jewelry
:
It displays the jewelry page as expected. Huzzah!
And for the sake of completeness, let’s try explicitly setting the audience to “clothing” by going to http://localhost:3000/?audience=clothing
:
The browser shows the clothing page as expected.
Step 8: Deploy to Netlify
Now that you’ve confirmed everything’s working on localhost, deploy to Netlify where everyone can see your hard work.
Netlify provides several ways to deploy a site. Our recommendation is to upload your site’s code to a git repository using one of their supported providers, then import your site from the repo.
Step 9: Change the preview URL
Remember when you set the preview URL for your page model to http://localhost:3000
?
It’s standard practice after publishing your site to change that development URL to your production URL. That way, your teammates can edit pages without having to spin up a local development environment.
Click Models in the left nav within the Builder dashboard and click Page.
On the Model Options page, enter your published site’s URL as the preview URL:
Click Save when you’re done.
Conclusion
Congratulations! You’ve successfully created a super-performant and personalized site using Next.js SSG and Netlify Edge Functions. And it’s integrated with Builder.io, so you can add more pages and content with ease.
You can continue adding to your site, using Builder’s targeting feature to create new content for different segments of your user base. Or use your newfound knowledge to integrate Builder with your current codebase and deploy to Netlify.