Skip to content

Commit

Permalink
initial example showing how to use Confidence with next.js
Browse files Browse the repository at this point in the history
  • Loading branch information
msvens committed Feb 2, 2025
1 parent 2a56dd6 commit 0f5b21a
Show file tree
Hide file tree
Showing 23 changed files with 635 additions and 0 deletions.
282 changes: 282 additions & 0 deletions examples/nextjs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
# About this Example
This example is an end-to-end walkthrough of how to set up a
simple next.js App that uses Confidence to experiment on what
button style you should use for your website. It will show you
how to set things up in Confidence as well as setting up your
website so you can eventually start to experimenting on it.

The intention is that you should be able to just use this readme
to set everything up. Of course you can always just go ahead and
clone this repo and only focus on prepping your Confidence for
it.

## Install the stuff you need

1. __npx create-next-app@latest__ (Just do the recommended choices).
2. `yarn add @spotify-confidence/sdk`
3. `yarn add @spotify-confidence/react`

## Prepare Confidence
In order for this example to work we need to make some prep work
on the confidence side. In particular we need to
* Create the __[flag](https://app.confidence.spotify.com/flags)__ button-style and the __override rules__ (new_button_style_visitor and default_button_style_visitor) for that

<img src="button-style-flag.png" alt="drawing" width="400"/>

* Create the __[event](https://app.confidence.spotify.com/events/)__ button-navigation we will track.

<img src="button-navigation-event.png" alt="drawing" width="400"/>

Make sure to also add the visitor_id as a type Entity to the context in your schema

<img src="event-schema.png" alt="drawing" width="400"/>


* Setup the __[client](https://app.confidence.spotify.com/admin/clients)__ we will use to interact with Confidence from our app. You wil likely already have a default client that you can use



## Setup of Confidence

Once we have everything up and running on the Confidence side we can
go ahead and integrate it to our website. The idea with this example
is to test a new button layout (inverted) on the frontpage. We will use
our override rules to make sure that we get the flag value we expect.

The first thing we need to do is to connect your website with Confidence. Since
the confidence provider relies on some client only features we need
to make sure to always use _'use client'_ whenever we use the provider.Thus we need
to make sure to make sure that our entire App is wrapped around it
if we want to set some things like our visitor_id in the global context.

The simplest way of doing that is to creat a component that you
will wrap your entire application in like so (see rootconfprovider.tsx):

```javascript
'use client'

import {ConfidenceProvider} from '@spotify-confidence/react';
import {ReactNode} from "react";
import {Confidence, visitorIdentity} from "@spotify-confidence/sdk";

if (!process.env.NEXT_PUBLIC_REACT_APP_CLIENT_SECRET) {
console.error('NEXT_PUBLIC_REACT_APP_CLIENT_SECRET not set in .env');
process.exit(1);
}

const cf = Confidence.create({
clientSecret: process.env.NEXT_PUBLIC_REACT_APP_CLIENT_SECRET,
environment: 'client',
region: 'eu',
timeout: 1000,
logger: console,
});

cf.track(visitorIdentity());
cf.setContext({visitor_id: 'new_button_style_visitor'})
//cf.setContext({visitor_id: 'default_button_style_visitor'})
//cf.setContext({ visitor_id: visitorIdentity().toString() })


type RootConfProviderProps = {
children: ReactNode,
}

export default function RootConfProvider({children}:RootConfProviderProps) {
return (
<ConfidenceProvider confidence={cf}>
{children}
</ConfidenceProvider>
)
}
```
Next you should locate `layout.tsx` and make sure our App uses the
RootConfProvider we just created:

```typescript jsx
import RootConfProvider from "@/app/rootconfprovider";

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<RootConfProvider>{children}</RootConfProvider>
</body>
</html>
);
}
```
Doing it this way will allow you to use server compiled pages that does not
require client only features, e.g. needs the _use client_ directive.

### Using our Confidence Flag

The final step is of course to use the flag that we created before.
For this we will first create our own special button that will record
an event in Confidence if it is clicked. It will also change layout depending
on if it should be inverted or not:

```typescript jsx
'use client'

import {useConfidence} from "@spotify-confidence/react";
import {useRouter} from "next/navigation";
import {ReactNode} from "react";

export type MTButtonProps = {
inverted?: boolean;
onClick?: () => void;
children: ReactNode,
href?: string;
trackEvent?: string;
}


export function ConfidenceButton(props: MTButtonProps) {

const invertedCN= "rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
const nonInvertedCN = "rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"

const confidence = useConfidence();
const router = useRouter();

const cn = props.inverted ? invertedCN : nonInvertedCN

function onClick() {
if(props.trackEvent) {
confidence.track(props.trackEvent);
}
if(props.href) {
router.push(props.href)
}


}

return (
<button className={cn} onClick={onClick}>
{props.children}
</button>
)
}
```
Observe that this button also need the _use client_ directive as it
uses the confidence provider to track events.

Additionally we will create _Flags.tsx_ file where we will store the definitions of our flags
(currently just one) as well as a utility function to print flag data if we like

```typescript jsx
'use client'
import {useConfidence} from "@spotify-confidence/react";

export type ButtonStyleFlag = {
inverted: boolean;
}

export function Flags() {
const confidence = useConfidence();
const flagData = JSON.stringify(confidence.useEvaluateFlag('button-style',
{"inverted": false}), null, ' ');
// const flagData = useDeferredValue(confidence.useFlag('web-sdk-e2e-flag.str', 'default'));
return (
<fieldset>
<legend>Flags</legend>
<pre>{flagData}</pre>
</fieldset>
);
}
```

The final step is use our newly created button in our App. We will add it to our
it to our root page like so:

```typescript jsx
'use client'

import Image from "next/image";
import {ConfidenceButton} from "@/components/ConfidenceButton";
import {useConfidence} from "@spotify-confidence/react";
import {ButtonStyleFlag} from "@/components/Flags"

export default function Home() {

const confidence = useConfidence();
const buttonStyle: ButtonStyleFlag = confidence.useFlag('button-style', {inverted: false});

return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
<h1 className={"w-max text-2xl font-bold"}>Confidence on Next.js</h1>
<div className="flex gap-4 items-center flex-col sm:flex-row">
<ConfidenceButton inverted={buttonStyle.inverted} trackEvent={"button-navigation"} href={"/page1"}>Button Style</ConfidenceButton>
</div>
</main>
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://confidence.spotify.com/docs"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/file.svg"
alt="File icon"
width={16}
height={16}
/>
Read the Confidence Docs
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://github.com/spotify/confidence-sdk-js/tree/main/examples"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/window.svg"
alt="Window icon"
width={16}
height={16}
/>
More JS Examples
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://confidence.spotify.com/"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/globe.svg"
alt="Globe icon"
width={16}
height={16}
/>
Go to your Confidence Space
</a>
</footer>
</div>
);
}
```
Again observe the need for the _use client_ directive as we are resolving the flag to
inform what style our button should have

## Test your application
Before you start your application dont forget to add your _.env_ file with the client secret
```
NEXT_PUBLIC_REACT_APP_CLIENT_SECRET=your_client_secret
```
Once that is done you can go ahead and run
```bash
yarn dev
```
Binary file added examples/nextjs/button-navigation-event.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/nextjs/button-style-flag.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions examples/nextjs/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const compat = new FlatCompat({
baseDirectory: __dirname,
});

const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
];

export default eslintConfig;
Binary file added examples/nextjs/event-schema.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions examples/nextjs/next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
/* config options here */
};

export default nextConfig;
30 changes: 30 additions & 0 deletions examples/nextjs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "testnext",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@spotify-confidence/react": "^0.0.10",
"@spotify-confidence/sdk": "^0.2.2",
"next": "15.1.6",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "15.1.6",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
8 changes: 8 additions & 0 deletions examples/nextjs/postcss.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};

export default config;
1 change: 1 addition & 0 deletions examples/nextjs/public/file.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/nextjs/public/globe.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/nextjs/public/next.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/nextjs/public/vercel.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/nextjs/public/window.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/nextjs/src/app/favicon.ico
Binary file not shown.
21 changes: 21 additions & 0 deletions examples/nextjs/src/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
--background: #ffffff;
--foreground: #171717;
}

@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}

body {
color: var(--foreground);
background: var(--background);
font-family: Arial, Helvetica, sans-serif;
}
Loading

0 comments on commit 0f5b21a

Please sign in to comment.