Plug Next
The official Croct SDK for Next.js applications.
📦 Releases
·
🐞 Report Bug
·
✨ Request Feature
The Next.js Plug library provides components and utilities for personalizing applications in real-time that are easy for your marketing team to scale and maintain.
The following steps will walk you through installing the library and integrating it into your Next.js application.
The recommended way to install the library is via NPM.
Run the following command to add the client as a dependency to your project and then install it:
npm install @croct/plug-next
Before you can start using the library, you need to set the environment variables and configure a middleware to populate the request scope with the client context.
Create or update the .env.local
file in the root of your project and add the following environment variables:
CROCT_API_KEY=<API_KEY>
NEXT_PUBLIC_CROCT_APP_ID=<APP_ID>
You can find your API Key and Application ID in the Croct dashboard under Workspaces > Applications > Setup
.
The API key is a secret key that should be kept confidential and never exposed to the client side. To create a new API key
go to Workspaces > Applications > API Keys
.
The configuration of the middleware slightly varies depending on whether you already have a middleware in your application or not.
If your application does not already have middleware, create a new one in the root of your project and add the following code:
export {config, middleware} from '@croct/plug-next/middleware';
Suppose you already have a middleware in your application like the following example:
import {NextResponse, NextRequest} from 'next/server';
export const config = {
matcher: ['/old'],
}
export function middleware(request: NextRequest): NextResponse|void {
return NextResponse.rewrite(new URL('/new', request.url))
}
For these cases, the library provides a higher-order function to wrap your existing middleware:
import {withCroct} from '@croct/plug-next/middleware';
function redirect(request: NextRequest): NextResponse|void {
if (request.nextUrl.pathname.startsWith('/old')) {
return NextResponse.rewrite(new URL('/new', request.url))
}
}
export {config} from '@croct/plug-next/middleware';
export const middleware = withCroct(redirect);
Note that the middleware must be run for all routes where you want to personalize content, which usually means all page routes except for static assets. If you are using a router matcher, be sure to modify it to run the middleware for all personalized routes, which usually means moving the logic into your custom matcher, as shown in the examples above.
Another important point is that if your middleware calls NextResponse.next()
, you must ensure that headers are forwarded to the next middleware,
otherwise the client context will not be available in the request scope.
For example, suppose you have a middleware that logs the request headers:
import {NextResponse, NextRequest} from 'next/server';
function middleware(request: NextRequest): NextResponse {
console.log(request.headers.get('user-agent'));
return NextResponse.next();
}
Here is how you can adjust it to forward the headers:
import {NextResponse, NextRequest} from 'next/server';
function logHeaders(request: NextRequest): NextResponse {
console.log(request.headers.get('user-agent'));
return NextResponse.next({
headers: request.headers,
});
}
export {config} from '@croct/plug-next/middleware';
export const middleware = withCroct(logHeaders);
You connect Croct to your application with the <CroctProvider />
component. The <CroctProvider />
uses a regular React's
<Context.Provider />
to wrap your React app and make the SDK available anywhere in your component tree.
We suggest putting the <CroctProvider />
somewhere high in your app, above any component that might be personalized,
ideally in the top-level <App/>
component.
import {render} from 'react-dom';
import {CroctProvider} from '@croct/plug-next/CroctProvider';
function App() {
return (
<CroctProvider>
<div>
<h1>My first personalized app 🚀</h1>
</div>
</CroctProvider>
);
}
render(<App/>, document.getElementById('root'));
To fetch personalized content, you can use the fetchContent
function as shown in the example below:
import React, {Fragment, ReactElement} from 'react';
import {fetchContent} from '@croct/plug-next/server';
export default async function Page(): Promise<ReactElement> {
const content = await fetchContent('slot-id');
return (/* Render the content */);
}
You can use the evaluate
function or the cql
template literal tag to evaluate queries.
Both the cql
tag and the evaluate function are similar in functionality. The main difference
between them is that the cql
tag automatically handles query interpolation for you,
but it does not support specifying options. On the other hand, the evaluate function allows you to
specify options, but you must handle query interpolation manually. So if you don't need to specify
options or interpolation, you can use them interchangeably.
Here is an example of using the evaluate function:
import {ReactElement} from 'react';
import {evaluate} from '@croct/plug-next/server';
export default async function Example(): Promise<ReactElement> {
const isNewYork: boolean = await evaluate("location's city is 'New York'");
return (/* Render the content */);
}
And here is an example of using the cql
template literal tag:
import {ReactElement} from 'react';
import {cql} from '@croct/plug-next/server';
export default async function Example(): Promise<ReactElement> {
const cities = ['New York', 'Los Angeles', 'Chicago'];
const isTargetCity: boolean = await cql`location's city is in ${cities}`;
return (/* Render the content */);
}
By default, all users are considered anonymous. However, if your application has logged-in areas, you may want to link the Croct user profile with your application's user ID. This allows you to personalize the user experience consistently across devices and sessions.
The SDK offers two options for identifying users, which you can choose depending on how you manage user sessions in your application.
The first method is to provide the middleware with a function that resolves the user ID from the request, so that it can automatically handle the entire process of identifying and anonymizing users for you.
This is the recommended approach if you have a secure way to determine the user ID from the request, such as a session token or JWT token.
Here is an example of how to implement this function:
import {NextRequest, NextResponse} from 'next/server';
function resolveUserId(request: NextRequest): string|null {
const userToken = request.cookies.get('my-session-token');
if (userToken === null) {
return null;
}
try {
// The logic to extract the user ID from the token and validate it.
return userId;
} catch {
return null;
}
}
export {config} from '@croct/plug-next/middleware';
export const middleware = withCroct({userIdResolver: resolveUserId});
If you do not have a way to automatically identify users, you can use the identify
and anonymize
functions
to manually handle the identification and anonymization of users.
The example above shows an example using server actions:
// app/actions.ts
'use server';
import {anonymize, identify} from '@croct/plug-next/server';
export async function identifyUser(userId: string): Promise<void> {
await identify(userId);
}
export async function anonymizeUser(): Promise<void> {
await anonymize();
}
Now, suppose you have the following form in your application:
'use client';
import React, {FunctionComponent} from 'react';
import {login} from '@/app/services';
export const LoginForm: FunctionComponent = () => {
const onSubmit = async (form: FormData): Promise<void> => {
const username = form.get('username');
const password = form.get('password');
await login(username, password)
};
return (
<form action={onSubmit}>
<input type="text" name="username" />
<input type="password" name="password" />
<button type="submit">Login</button>
</form>
);
};
After successful login, you can identify the user as follows:
'use client';
import React, {FunctionComponent} from 'react';
import {login} from '@/app/services';
import {identifyUser} from '@/app/actions';
export const LoginForm: FunctionComponent = () => {
const onSubmit = async (form: FormData): Promise<void> => {
const username = form.get('username');
const password = form.get('password');
if (await login(username, password)) {
// Identify the user after successfully logging in
await identifyUser(username);
}
};
return (
<form action={onSubmit}>
<input type="text" name="username" />
<input type="password" name="password" />
<button type="submit">Login</button>
</form>
);
};
You can anonymize the user after logging out in the same way:
'use client';
import React, {FunctionComponent} from 'react';
import Link from 'next/link';
import {anonymizeUser} from '@/app/actions';
export const LogoutLink: FunctionComponent = () => (
<Link href="/login" onClick={() => anonymizeUser()}>
Anonymize
</Link>
);
If this documentation has not answered all your questions, don't worry. Here are some alternative ways to get help from the Croct community.
Someone else from the community may have already asked a similar question or may be able to help with your problem.
The Croct team will also monitor posts with the "croct" tag. If there aren't any existing questions that help, please ask a new one.
If you have what looks like a bug, or you would like to make a feature request, please open a new issue on GitHub.
Before you file an issue, a good practice is to search for issues to see whether others have the same or similar problems. If you are unable to find an open issue addressing the problem, then feel free to open a new one.
Many people from the Croct community hang out on the Croct Slack Group. Feel free to join us and start a conversation.
This project is released under the MIT License.