Tutorial
Developer preview
In this tutorial, you build a Next.js app that renders pages from Uniform. At the end, you can:
- Render a Uniform composition at its URL.
- Edit the page in Canvas, with visual editing.
- Show a different hero to visitors from a campaign.
- Show the correct hero on the first paint, with edge mode.
The tutorial takes about one hour. If you do not know the Uniform terms, read Core concepts first.
What you build#
Before you start#
You must have:
- Node.js 20.9 or later
- A Uniform team, and an empty Uniform project
- An API key for the project with permission to read and write content. Refer to API access.
Part 1: Prepare the Uniform project#
Do these steps in Uniform. For the details of each screen, refer to the linked guides.
Step 1: Define the component types#
Make two component types in the component library. Refer to Components.
| Component type | Public ID | Composition component | Parameters | Slots |
|---|---|---|---|---|
| Page | page | Yes | None | content: allow Hero and Personalization |
| Hero | hero | No | title (Text), description (Rich text) | None |
The public IDs are important. Your code uses them.
Make the Hero type first. Then the content slot of Page can allow it. Allow the Personalization system component in the content slot too. You add a personalization to this slot in Part 5.
Step 2: Make a composition#
- Make a composition of the type Page, with the name Home. Refer to Compositions.
- Add a Hero to the
contentslot. - Set the title to "Hello World", and set a description.
- Publish the composition.
Step 3: Connect the composition to a URL#
In the project map, attach the Home composition to the root node /. Refer to Project maps.
Step 4: Get the API values#
Write down these values. You use them in Step 6.
- The project ID
- The API key
- A preview secret. This is a value that you choose, for example the output of
openssl rand -base64 32.
Part 2: Connect a Next.js app#
Step 5: Make the Next.js app and install the SDK#
Make a Next.js app with the App Router and TypeScript:
npx create-next-app@latest my-uniform-app cd my-uniform-appSelect the recommended Next.js defaults. They include the App Router, TypeScript, Tailwind CSS and the
@/*import alias. You change one of the defaults in Step 7.Install the SDK:
v=20.81.1-alpha.25.sha-f076f9b857 npm install @uniformdev/next-app-router@$v @uniformdev/richtext@$v@uniformdev/next-app-routercontains the server SDK, the components, the middleware, the configuration helpers and the preview handlers.@uniformdev/richtextgives the rich text type for the Hero.
warning
Use the same version for all @uniformdev packages. Different versions can install two copies of @uniformdev/context, and then the browser Context does not work correctly.
Step 6: Set the environment variables#
Make .env.local in the project root:
For all the variables, refer to Set the environment variables.
Step 7: Wrap the Next.js configuration#
create-next-app makes a next.config.ts file. Change it to this:
next.config.ts
- Wrap the configuration with
withUniformConfig. - Remove
cacheComponents: trueandpartialPrefetching: true. This tutorial does not use Cache Components. With Cache Components, the route of Step 10 fails. To use Cache Components later, refer to Caching. - Keep the other options. If you remove the
turbopackrule, Tailwind CSS stops working.
withUniformConfig looks for uniform.server.config in the project root, with the extension .js, .cjs, .mjs or .ts. When it finds the file, it adds an alias for Turbopack, and for the webpack server build. Then the SDK reads your configuration.
Step 8: Add the server configuration#
uniform.server.config.ts
defaultConsent: truelets the Uniform Context store visitor data without a consent banner. Set it tofalseif your site asks for consent.playgroundPathis the route that shows patterns in Canvas (Step 11).
Your file replaces the default configuration fully, so always set these two options. For all the options, refer to Server configuration.
Step 9: Add the middleware#
Make middleware.ts in the project root. The middleware writes the route and the request state into a code, and rewrites the request to /uniform/[code]. It makes no network calls.
middleware.ts
This is lite mode: the browser chooses the personalization variants. You change to edge mode in Part 5.
Keep middleware.ts on the edge runtime
Next.js 16 renames middleware.ts to proxy.ts. When you start the app, Next.js shows 3 warnings: "The "middleware" file convention is deprecated", "You are using an experimental edge runtime" and "The Edge Runtime is deprecated". These warnings are expected. Do not rename the file, and do not run the codemod that Next.js recommends. On Vercel, the SDK cannot read draft mode in proxy.ts (vercel/next.js#82344), and Canvas preview shows published content. Keep middleware.ts and runtime: "experimental-edge".
For all the options, refer to Middleware configuration.
Step 10: Add the composition route#
Make app/uniform/[code]/page.tsx. The middleware rewrites each request to this route:
app/uniform/[code]/page.tsx
UniformComposition is an async server component. It does these tasks:
It resolves the route from the code with
resolveRouteFromCode. To use another resolver, setresolveRoute, for example the'use cache'resolver from Caching.It applies the result:
- A Uniform redirect with the status 301 or 308 calls
permanentRedirect(), and Next.js sends a 308. - A redirect with a different status calls
redirect(), and Next.js sends a 307. - A missing route calls
notFound().
Next.js does not keep the exact status code. If the page streams already, for example below a
loading.tsxfile, Next.js redirects in the browser with a meta tag.- A Uniform redirect with the status 301 or 308 calls
It renders the composition in
UniformContext.UniformContextstarts the Uniform Context in the browser. In preview, it also adds the visual editing script.
Do not put UniformContext in layout.tsx. UniformComposition adds it for you.
| Prop | Required | Description |
|---|---|---|
code | Yes | The [code] path segment from the middleware. |
resolveComponent | Yes | Your component resolver (Step 13). |
resolveRoute | No | The route resolver. The default is resolveRouteFromCode. |
clientContextComponent | No | A custom client context. |
resolveEmptyPlaceholder | No | The component for an empty slot in Canvas. |
compositionCache | No | A composition cache. |
warning
With Cache Components (cacheComponents: true), an empty generateStaticParams makes the build fail. Return at least one code, for example createUniformStaticParams({ paths: ["/"] }). Refer to Static generation (ISR).
Step 11: Add the playground route#
Canvas shows patterns on the playground route. Make app/playground/[code]/page.tsx:
app/playground/[code]/page.tsx
The middleware sends pattern previews to ${playgroundPath}/[code] in draft mode. If UniformPlayground renders outside draft mode, it shows the message "Playground is only available in draft mode".
Step 12: Add the preview and webhook handler#
Make app/api/preview/route.ts:
app/api/preview/route.ts
GETstarts a preview from Canvas. It checksUNIFORM_PREVIEW_SECRET, enables Next.js draft mode, and redirects to the page. Refer to Preview.POSTreceives Uniform webhooks, and revalidates the cache tags and paths of the changed content. Refer to Caching.OPTIONSanswers CORS preflight requests from Canvas.
Part 3: Build the components#
Step 13: Map Uniform types to React components#
Make components/resolveComponent.tsx. The SDK calls this function for each component in the composition:
components/resolveComponent.tsx
The keys of componentMap are the public IDs from Step 1. An unknown type renders the NotFound component, so a new component type in Uniform does not break the page.
Step 14: Build the Page component#
The Page component is the root of the composition. It renders its content slot:
components/page.tsx
UniformSlot renders the components that authors put in the slot, in their order. For more ways to render slots, refer to Components and slots in code.
Step 15: Build the Hero component#
components/hero.tsx
- The parameter names are the public IDs of the parameters from Step 1.
- Make all parameters optional with
?. A parameter can beundefined, for example when an author adds a component and does not fill it in. UniformTextandUniformRichTextlet authors edit the text on the page in Canvas. They require a parameter object, so render them only when the parameter exists.
For all the props and helpers, refer to Components and slots in code.
Step 16: Run the app#
Open http://localhost:3000. The page shows the "Hello World" hero from Uniform.
If the page shows a 404, make sure that the composition is published, and that the project map node / has the composition.
Part 4: Edit in Canvas#
Step 17: Set the preview URL#
In the settings of your Uniform project, set the preview URL to:
Use the same secret as UNIFORM_PREVIEW_SECRET. Refer to Visual editing.
Step 18: Edit the page#
- Open the Home composition in Canvas. The preview panel shows your app.
- Select the title of the hero on the page, and type a new title. The page shows the change as you type.
- Add a second Hero to the
contentslot. The page shows it.
In preview, the SDK renders draft content. You see changes before you publish them. For how preview works, refer to Preview.
note
If Canvas shows published content and not your changes, make sure that the middleware file is middleware.ts with runtime: "experimental-edge" (Step 9). For more causes, refer to Troubleshooting.
Step 19: Publish#
Publish the composition. Then open http://localhost:3000 outside Canvas. The page shows the published content.
Under npm run dev, the SDK does not cache content, so you see the change at once. In production, a webhook revalidates the cache (Part 6).
Part 5: Add a personalization#
Step 20: Make a signal#
In Uniform, make a signal with the name Launch campaign. Set its criteria to the query string utm_campaign with the value launch. Publish the Context manifest. Refer to Signals.
Step 21: Personalize the hero#
- In Canvas, add a personalization to the
contentslot of Home. Refer to Personalization. - Set the Analytics tracking name of the personalization, for example
home-hero. This field is required. If it is empty, you cannot publish. - Put the "Hello World" hero in the personalization as the default variant. Do not set criteria on it.
- Add a second hero with the title "Welcome, launch visitor". Set its criteria to the Launch campaign signal. Put this hero above the default variant. The personalization selects the first variant that matches, and the default variant matches all visitors.
- Publish the composition.
Step 22: Look at lite mode#
Open http://localhost:3000/?utm_campaign=launch. The page shows "Hello World" first. After the JavaScript loads, it changes to "Welcome, launch visitor".
This is lite mode. The server renders the default variant, and the browser chooses the variant of the visitor after hydration. The change on the screen is the "flicker".
Step 23: Turn on edge mode#
Edge mode chooses the variant at the edge, before the browser gets the page.
Install the Vercel functions package:
npm install @vercel/functionsReplace the middleware:
middleware.ts
import { vercelUniformEdgeMiddleware } from "@uniformdev/next-app-router/vercel"; export default vercelUniformEdgeMiddleware(); export const config = { matcher: [ { source: "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)", // The middleware fetches the cached page with this header. Do not run it again. missing: [{ type: "header", key: "x-uniform-edge-origin" }], }, ], runtime: "experimental-edge", };Expire the edge cache when you publish:
app/api/preview/route.ts
import { createPreviewGETRouteHandler, createPreviewOPTIONSRouteHandler, createPreviewPOSTRouteHandler, } from "@uniformdev/next-app-router/handler"; import { expireVercelRuntimeCacheTags } from "@uniformdev/next-app-router/vercel"; export const GET = createPreviewGETRouteHandler(); export const POST = createPreviewPOSTRouteHandler({ onRevalidateTags: expireVercelRuntimeCacheTags }); export const OPTIONS = createPreviewOPTIONSRouteHandler();Edge mode works on published pages from the cache. Build and start the app:
npm run build && npm run startOn your computer, the Vercel runtime cache is not available. The log shows "Runtime Cache unavailable in this environment. Falling back to in-memory cache." This is expected. On Vercel, the SDK uses the runtime cache.
Open
http://localhost:3000/?utm_campaign=launch. The page shows "Welcome, launch visitor" on the first paint, with no change.
To look at the HTML from the server:
For how edge mode works, refer to Edge mode execution.
Part 6: Deploy#
Step 24: Deploy to Vercel#
- Add the environment variables of Step 6 to the Vercel project, for the Production environment. Make sure that the values do not have a space or a line break at the end. A line break in
UNIFORM_PROJECT_IDmakes all pages show a 404. - Deploy the app to production. Use the production URL, for example
https://your-site.vercel.app. Vercel protects preview deployments by default, and then Canvas and the webhook cannot get to the app. - In Uniform, change the preview URL to
https://your-site.vercel.app/api/preview?secret=your-preview-secret.
Step 25: Add the webhook#
In Uniform, add a webhook with the URL https://your-site.vercel.app/api/preview?secret=your-preview-secret.
Select these events:
composition.publishedcomposition.deletedentry.publishedentry.deletedprojectmap.node.insert,projectmap.node.updateandprojectmap.node.deleteredirect.insert,redirect.updateandredirect.deletemanifest.published
When an author publishes, the webhook revalidates the pages that use the content. Refer to Caching.
Step 26: Do a test of the deployment#
- Publish a change to the Home composition.
- Open the site. The page shows the change. If it does not, open the site again: the first request can get the old page while Vercel renders the new page.
- Open
https://your-site.vercel.app/?utm_campaign=launch. The page shows the personalized hero on the first paint.
Next steps#
- Add more components and slots: Components and slots in code
- Add a header, a footer, images, links and metadata: Recipes
- Select the setup for your app: Choose a setup
- Test your components: Testing
- Fix problems: Troubleshooting