Complete guide to Nuxt and Prepr personalization

Estimated duration: 20 minutes

This guide shows you how to connect Prepr to a Next project including styling and personalization. You can then customize this project to fit the requirements for your web app.

Introduction

This is a step-by-step guide that shows you how to set up a working front-end app with personalization. You'll use Nuxt 3 for the front end and connect to Prepr content that’s set up with personalization.

You are going to build this front end in the following steps:

At the end of this guide, you’ll have a working website with personalization like the image below. On the left, you can see a site connected to Prepr with a home page. On the right is the same page that has been personalized for a specific target audience (Beginner bakers).

web app end result

Visit our demo website to see a personalization example in action.

Prerequisites

You need to have the following setup before you connect your Nuxt project to Prepr.

Note

Step 1: Create a new Nuxt project

This guide is based on Nuxt 3. The instructions below will guide you on how to create an empty Nuxt project for your app.

If you have an existing Nuxt 3 project then you can skip this step.

  1. Open a terminal and execute the following command to create a new Nuxt project called prepr-personalization:
npx nuxi init prepr-personalization

When the project is successfully created, go to the prepr-personalization folder, the root directory of the project, and execute the following commands:

  1. When the project is successfully created, go to the prepr-personalization folder, the root directory of the project, and execute the following commands:
npm install
npm run dev
  1. You should now be able to view your app on your localhost, for example, http://localhost:3000/.

  2. Open your Nuxt project with your preferred code editor.

  3. Update the app.vue file with the following code to display your blog:

<!-- ./app.vue -->

<template>
  <div>
    <h1>My home page</h1>
  </div>
</template>

You should now see something like the image below on your localhost.

New project

Once your Nuxt project is created and working, add some styling.

Step 2: Install Tailwind

Tailwind is a CSS framework that simplifies front-end styling. Use a Nuxt Tailwind module for the styling in this project.

  1. Stop the server you started in the above step (CTRL-C) and execute the following command in the terminal to install Tailwind:
npm install --save-dev @nuxtjs/tailwindcss
  1. Add the Tailwind module to the nuxt.config.ts file with the following code:
// ./nuxt.config.ts 

import { defineNuxtConfig } from 'nuxt/config'

export default defineNuxtConfig(
{
  // Add the tailwind module in the line below
  modules: [
    '@nuxtjs/tailwindcss'
  ]
})
  1. Test the Tailwind installation by adding styling to the heading in the app.vue file as follows:
<!-- ./app.vue -->

<template>
  <div>

    <!-- Add tailwind class to include styling in the heading -->
    <h1 class="mb-4 bg-black text-3xl font-extrabold text-white md:text-6xl lg:text-6xl">
      My blog site
    </h1>
  </div>
</template>

Run the app again by executing the command:

npm run dev

Now, when you visit the localhost, you should see a styled heading in your app like in the image below:

styled example

Step 3: Create a static page

In this step, you'll create a static version of the page including two front-end components. Later, you'll update them to fetch the corresponding data dynamically from Prepr.

As you can see in the image below, there is a Home page content item in the demo data in Prepr. The Home page has all the elements of the page in a Stack field. The stack makes it easy for an editor to set up their page content in a flexible structure for the front end. Check out the page pattern doc for more details on how to use the Stack field.

For this project, you are going to implement this page and two components: Page header and Image and text. In this step, you'll first implement these as static components. Later in the guide, you'll retrieve them from Prepr to make them dynamic.

Home page content item

Add the components to the front end as follows:

  1. Update the app.vue file with the following code to display the components with static text:
<!-- ./app.vue -->

<template>
  <div>
    <PageHeader
      :data="{
        heading: 'My static site',
        text: 'This is static placeholder text',
        image: [
          {
            'url': 'https://placehold.co/1320x400?text=Page%20header%20image'
          }
        ],
        cta_label: 'Get more info',
      }"
    >
    </PageHeader>
    <ImageAndText
      :data="{
        title: 'Static text title',
        text: 'Static text body',
        image: [
          {
            'url': 'https://placehold.co/150x150?text=Image%20and%20Text%20image'
          }
        ],
        image_position: 'Right',
      }"
    >
    </ImageAndText>
  </div>
</template>
  1. In the root directory of your Nuxt project create a components folder with new files called PageHeader.vue and ImageAndText.vue.

  2. Copy the code below into the PageHeader.vue file to construct the page header.

 <!-- ./components/PageHeader.vue -->

 <template>
    <!-- Set background image -->
    <section
      :style="`background-image: url(${data.image[0].url})`"
      class="my-12 flex items-center h-[600px]"
      :class="`
        bg-no-repeat bg-cover
      `"
    >
      <div class="container mx-auto md:px-0">
        
        <h1 
          class="mb-4 text-3xl font-extrabold leading-none tracking-tight text-white md:text-6xl md:text-5xl lg:text-6xl"
        >
            <!-- Set heading -->
            {{ data.heading }}
        </h1>
       
        <p
          class="mb-8 text-base font-normal text-white md:text-lg lg:text-xl"
        >
             <!-- Set main text of page header -->
             {{ data.text }}
        </p>
        <a
          href="#" 
          class="
            inline-flex
            items-center
            px-6
            py-3.5
            text-sm
            font-medium
            text-center text-white
            rounded-lg
            bg-violet-700
            hover:bg-violet-800
            focus:ring-4 focus:outline-none focus:ring-violet-300
          "
        >
             <!-- Set the Call to action link in the page header-->
             {{ data.cta_label }}
          <svg
            aria-hidden="true"
            class="w-5 h-5 ml-2 -mr-1"
            fill="currentColor"
            viewBox="0 0 20 20"
            xmlns="http://www.w3.org/2000/svg"
          ><path
              fill-rule="evenodd"
              d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z"
              clip-rule="evenodd" 
            ></path></svg>
        </a>
      </div>
    </section>
  </template>

  <!-- Add a script to fetch the data -->
<script setup>

import { computed } from "vue";

// Pass the data as props to the page
const props = defineProps(["data"]);
const data = computed(() => props.data);
const hasImage = computed(() => "image" in props.data);

</script>
  1. Copy the code below into the ImageAndText.vue file to construct the image and text element of the page.
<!-- ./components/ImageAndText.vue -->

<template>
    <div class="container mx-auto md:px-0">
      <section class="bg-white">
        <div
          class="items-center max-w-screen-xl gap-8 px-4 py-8 mx-auto  xl:gap-16 md:grid md:grid-cols-2 sm:py-16 lg:px-6"
        >
        <!-- Put image on page to the left or right based on the position defined in Prepr -->
        <img
          :class="data.image_position === 'Right' ? 'order-last' : ''"
          class="object-cover w-full rounded-lg shadow-xl  h-60 md:h-96"
          :src="data.image[0]?.url"
          alt="dashboard image"
        />
          <div class="mt-8 md:mt-4 md:mt-0">
            <!--Text heading-->
            <h2
              class="mb-4 text-xl font-extrabold tracking-tight text-center text-gray-900  md:text-left md:text-4xl"
            >
            {{ data.title }}
            </h2>
            <!--Text body -->
            <p
              class="font-light text-center text-gray-500  md:mb-6 md:text-left md:text-lg"
            >
            {{ data.text }}
            </p>
          </div>
        </div>
      </section>
    </div>
  </template>

  <!-- Add a script to fetch the data -->
<script setup>

import { computed } from "vue";

// Pass the data as props to the page
const props = defineProps(["data"]);
const data = computed(() => props.data);

</script>

You should now see something like the image below on your localhost:

Static site

Great work on getting your static web app working! Continue the following steps to make it dynamic by including data from Prepr.

Step 4: Install the Nuxt Apollo client

The Apollo client is an integration tool that helps to retrieve CMS data with GraphQL. The instructions below show you how to install the Nuxt Apollo client so that you can execute GraphQL queries to request data from the Prepr API.

  1. Stop the server you started in the above step (CTRL-C) and run the following command in the terminal:
npm i -D @nuxtjs/apollo@next
  1. Create a folder called apollo in the root directory of your project. Then, create a file called prepr.ts in this folder. Copy the following code to this file to import the Apollo client:
// ./apollo/prepr.ts 

import { defineApolloClient } from "@nuxtjs/apollo";

export default defineApolloClient({
  httpEndpoint: "https://graphql.prepr.io/graphql",
  defaultOptions: {},
  inMemoryCacheOptions: {},
  tokenName: "apollo:prepr.token",
  tokenStorage: "cookie",
  authType: "Bearer",
  authHeader: "Authorization",
  httpLinkOptions: {
    headers: {
      Authorization: `Bearer ${process.env.PREPR_ACCESS_TOKEN}`,
    },
  },
});
  1. Next, open the nuxt.config.ts file and add the apollo module with the following code:
// nuxt.config.ts

import { defineNuxtConfig } from 'nuxt/config'

export default defineNuxtConfig({
  // Add Apollo module 
  modules: ['@nuxtjs/tailwindcss', '@nuxtjs/apollo'],

  // Add apollo client config
  apollo: {
    clients: {
      prepr: './apollo/prepr.ts',
    },
  },

  // If deploying with Vercel, add config below
  build: {
    transpile: ["tslib"],
  },
})
  1. We recommend using environment variables to store sensitive information like access tokens. To add environment variables, create a .env file in the root directory of your project and add the access token like this:
# ./.env

PREPR_ACCESS_TOKEN=<YOUR-ACCESS-TOKEN>
  1. Replace the placeholder value <YOUR-ACCESS-TOKEN> with an access token from Prepr. Get an access token by logging into your Prepr account:Get an access token by logging into your Prepr account:

    a. Go to Settings > Access tokens to view all the access tokens.

    b. Copy the GraphQL Production access token to only retrieve published content items.

access token list

Note

Use the GraphQL Production access token to request published content items for your live app and use the GraphQL Preview token to make a preview of unpublished content items for your content editors.

  1. Execute the following commands to make sure that the Apollo client is installed correctly:
npm run dev

If your app runs without errors, then the setup above was done correctly. The next step is to fetch content from Prepr using the installed Apollo client.

Once the apollo client is installed successfully, you can fetch the Home page content from Prepr using GraphQL queries.

Step 5: Make the page dynamic

Now that your Apollo client is installed and connected to Prepr, fetch the page content from Prepr.

In this example, there is only one page, but when you extend this project you'll create multiple pages. To manage the routing to these different pages, set up dynamic routing.

Set up dynamic routing

Nuxt reads all the Vue files inside a pages directory and automatically creates the router configuration, so setting up the routing is simple.

  1. To set up the automatic routing, create a pages folder.

  2. Move the app.vue file to the pages folder and rename it to index.vue.

When you rerun your app, you can see the same static page as it was rendered previously.

Check out the Nuxt dynamic routing docs for more details.

Add a GraphQL query

Next, create a query to get the content from Prepr as follows:

  1. Create a queries folder in the root directory of your project and create a file named get-page-by-slug.js.

  2. Add the following query to this file to retrieve a page by its slug:

// ./queries/get-page-by-slug.js

export const GetPageBySlug = gql`
  query ($slug: String) {
    Page(slug: $slug) {
      _id
      title
      _slug

      # Retrieve the stack and two components: 
      # Page Header and Image and Text 
      stack {
        __typename
        ... on PageHeader  {
          heading
          cta_url
          cta_label
          image {
            url(width: 1600)
          }
          _id
          text
        }
        ... on ImageAndText {
          image {
            url(width: 800)
          }
          text
          title
          image_position
          _id
        }
      }
    }
  }
`;

Tip

You can create and test GraphQL queries using the Apollo explorer from Prepr. Open the API Explorer from the Article content item or the access token that you copied previously.

  1. Test the query by adding a script to the bottom of the index.vue file in the pages folder. This script imports the query you just created, executes the query with a slug value and prints the query results to the console.
<!-- ./pages/index.vue --> 

<template>
  <div>
    <PageHeader
      :data="{
        heading: 'My static site',
        text: 'This is static placeholder text',
        image: [
          {
            'url': 'https://placehold.co/1320x400?text=Page%20header%20image'
          }
        ],
        cta_label: 'Get more info',
      }"
    >
    </PageHeader>
    <ImageAndText
      :data="{
        title: 'Static text title',
        text: 'Static text body',
        image: [
          {
            'url': 'https://placehold.co/150x150?text=Image%20and%20Text%20image'
          }
        ],
        image_position: 'Right',
      }"
    >
    </ImageAndText>
  </div>
</template>

<!--The script executes the query, assigns a variable to the returned results and prints the results to the console-->
<script setup>
  import { computed } from "vue";
  import { reactive } from "vue";

  // Import the query
  import { GetPageBySlug } from "@/queries/get-page-by-slug";

  // Execute query to retrieve a page by a slug
  const { data } = await useAsyncQuery(
    GetPageBySlug,
    {
      slug: "home",
    }
  );

  // Print the query result to the console
  console.log(JSON.stringify(data, undefined, 2));

</script>

If you’re using preloaded demo data in your Prepr environment as mentioned above in the Prerequisites section, you should have one home page with the slug home. The query will retrieve the Id, title, slug, and a stack containing a page header and two image and text blocks with their own fields.

When you check the console, the response to the query looks something like this:

query results in console

Now that the query has been created and retrieves the data successfully, fetch the specific page content from the query results.

Fetch page content

To view the Prepr content in the web app, you need to fetch the page elements. As mentioned previously, the page has elements in a Stack field. You'll fetch two components, Page header and Image and Text in the stack.

Construct the page by updating the code in the index.vue as follows:

  • Update the script to import the components, list the components that you want to include in the page and fill the page and stack variables with the queried content.
  • Update the template with dynamic code to loop though this stack variable and fill the data variable for each component.
<!-- ./pages/index.vue-->

<template>

<!--Loop through elements in the queried stack and set the data variable to the components you want -->
  <component
    v-for="element in stack"
    :key="element._id"
    :is="getComponent(element.__typename)"
    :data="element"
  ></component>

</template>

<script setup>
  import { computed } from "vue";
  import { reactive } from "vue";
  import { GetPageBySlug } from "@/queries/get-page-by-slug";

  // Import the components
  import PageHeader from "@/components/PageHeader";
  import ImageAndText from "@/components/ImageAndText";

  // Set up the components
  const components = [
    { name: "PageHeader", comp: PageHeader },
    { name: "ImageAndText", comp: ImageAndText },
  ];

  // Assign the components for the stack loop above
  const getComponent = (name) => {
    const component = components.find((component) => component.name === name);
    return component ? component.comp : null;
  };

  const { data } = await useAsyncQuery(
    GetPageBySlug,
    {
      slug: "home",
    }
  );

  console.log(JSON.stringify(data, undefined, 2));

  // Assign the page and stack variables with the queried results
  const page = data.value.Page;
  const stack = computed(() => {
    return page.stack;
  });
  </script>

Now when you view the website on your localhost, you'll see something like the image below:

Dynamic home page

Congratulations! You have successfully connected your web app to Prepr. Go to your Home page content item in Prepr and make some content updates to see the effects on your web app immediately. For example, reorder the page header and the image and text blocks.

Step 6: Set up personalization

Now that you have a working web app with styling and content from Prepr, add personalization. For this project, assume the scenario below for the baking community web app.

A user comes across a campaign for beginner bakers in a social post. They click on the link in the post and get directed to your page. The URL link from this post includes a special UTM tag that identifies the specific campaign. Your web app uses the UTM tag to display the personalized page with the promotion details for the beginner baker.

Start by setting up the personalization in Prepr.

Personalize the page header in Prepr

You want to show a different version of the page header when a beginner baker visits the page. The setup for personalization in Prepr is made up of two parts. First you need to create customer segments and then add the personalization content. If you are using the preloaded demo data then you already have some customer segments defined like in the image below.

Preloaded customer segments

For this example, use the Beginner bakers segment. For more details, check out the Customer segments doc.

Go to your Home page content item in Prepr and add personalization as follows:

  1. Go to the page header in the stack and click on the icon on the page header to add the personalization.
  2. Duplicate the page header. The original page header will automatically be used as the fallback content and linked to All other users.
  3. Update the image and text for the Beginner bakers page header.
  4. Click the Select audience button and choose the Beginner bakers segment for this header and save the page.

add personalization

Now that the personalization is ready in the content, add some code to the front end.

Get the segment Id from the UTM tag

To fetch the personalized page, you need to get the UTM tag from the URL. In this example, the UTM tag matches the segment Id for Beginner bakers.

Update the index.vue file with the following code to get the UTM campaign value and to fetch the personalized content.

<!-- ./pages/index.vue -->

<template>
    <component
      v-for="element in stack"
      :key="element._id"
      :is="getComponent(element.__typename)"
      :data="element"
    ></component>  
</template>
  
  <script setup>
    import { computed } from "vue";
    import { reactive } from "vue";
    import PageHeader from "@/components/PageHeader";
    import ImageAndText from "@/components/ImageAndText";
    import { GetPageBySlug } from "@/queries/get-page-by-slug";

    // Add this import to access the URL values like the UTM campaign
    import { useRoute } from "vue-router";
  
    // Get the utm campaign value from the URL
    const route = useRoute();
    const utm_campaign = (route.query.utm_campaign === undefined) ? "None" : route.query.utm_campaign;

    const components = [
      { name: "PageHeader", comp: PageHeader },
      { name: "ImageAndText", comp: ImageAndText },
    ];
  
    const getComponent = (name) => {
      const component = components.find((component) => component.name === name);
      return component ? component.comp : null;
    };
  
    const { data } = await useAsyncQuery(
      GetPageBySlug,
      {
        slug: "home",

        // Set the segment to the utm_campaign value assigned above
        segment: utm_campaign,
      }
    );
  
    const page = data.value.Page;
  
    const stack = computed(() => {
      return page.stack;
    });
    </script>

Update the query

The current page query returns a page that is not personalized. You need to set a segment parameter in the query to get the personalized page. In this example, this value has to match the segment Id for Beginner bakers.

Update the query in the get-page-by-slug.js with this parameter and additional personalization fields as follows:

// ./queries/get-page-by-slug.js

export const GetPageBySlug = gql`

# Add variable for the segment
  query ($slug: String, $segment: String!) {
    Page(slug: $slug) {
      _id
      title
      _slug

      # Retrieve a variant based on the provided segment
      stack (personalize_for_segments: [$segment]) {
        __typename
        ... on PageHeader  {
          heading
          cta_url
          cta_label
          image {
            url(width: 1600)
          }
          _id
          text

          # Retrieve system fields with personalization info
          # The _context fields hold info about the personalization:
          #  - "kind" shows that this component includes personalization
          #  - "group_id" is a unique Id to identify a specific personalized grouping for multiple personalized components
          #  - "segments" is the segment that the variant is applicable to.
          #  - "countries" is filled if the variant is applicable to specific countries.
          _context {
            kind
            group_id
            segments
            countries
          }
        }
        ... on ImageAndText {
          image {
            url(width: 800)
          }
          text
          title
          image_position
          _id
        }
      }
    }
  }
`;

Now that the query has been updated to include personalized content, you can view the results.

View in the browser

When you view the site as is, you should still see the page without personalized content. Check the console for the updated results.

personalized query results

Add the UTM tag to the URL, for example: http://localhost:3000/?utm_campaign=beginner-bakers

Now you can see the personalized content such as in the image below.

Personalized page end result

All done

Congratulations! You have a working Nuxt web app connected to Prepr including personalization. This is a very simple example. You can personalize more elements like call-to-actions, featured articles and more. Also you can create dynamic segments based on what visitors do in your app. The options are limitless.

Next steps

To learn more on how to expand your project, check out the following resources: