Next.js tutorial (Page Router)

Newer version available

This tutorial is based on Next.js Page Router. If you are starting a new project now, you may want to start with App Router tutorial here, as it is now a default new way to build Next.js apps.

This tutorial describes setting up a Uniform-powered composable website in Next.js and Typescript. At the end of this tutorial, you'll learn to:

  • Develop a Next.js-powered website and connect your Uniform project.
  • Render content created in Uniform on your website.
  • Set up a preview of your website on Uniform, and continue building your website in a no-code environment.

Goals

  1. Familiarize yourself with Uniform and its elements.
  2. Model your content and add it to Uniform.
  3. Connect a Uniform composition to your local project using the Composition's slug.
  4. Activate preview for no-code editing in your project.

In this tutorial, you'll follow the following steps to build a Uniform-powered project.

Prerequisites

You'll need knowledge of:

  • HTML
  • CSS
  • JavaScript
  • React

The knowledge of Next.js and Typescript is optional but beneficial. On your machine, you require access to a terminal with Node.js and its package manager, npm, installed. Alternatively, you can use Yarn.

Choose a place on your local machine and in that location run the following command to create a Next.js project:

npx create-next-app@latest

Follow the prompts and select the following options:

PromptResponse
Project name?getting-started
Would you like to use Typescript?Yes
Would you like to use ESLint with this project?Yes
Would you like to use Tailwind CSS with this project?Yes
Would you like to use the src/ directory with this project? Y/NYes
Use App Router (recommended)?No
Would you like to customize the default import alias?No

The command proceeds to install dependencies in your project. Navigate to your project once done. TailwindCSS will style the components in this project.

info

Be sure you installed the latest create-next-app version to get the Tailwind installation instructions.

In your local project, navigate to the src/pages/index.tsx file. Replace the contents of the file with this:

src/pages/index.tsx

import Head from 'next/head' export default function Home() { return ( <> <Head> <title>Create Next App</title> <meta name="description" content="Generated by create next app" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="icon" href="/favicon.ico" /> </Head> <h1>Hello world</h1> </> ) }

Creating the project included installing Tailwind and creating the src/styles/global.css file:

@tailwind base; @tailwind components; @tailwind utilities; :root { --foreground-rgb: 0, 0, 0; --background-start-rgb: 214, 219, 220; --background-end-rgb: 255, 255, 255; } @media (prefers-color-scheme: dark) { :root { --foreground-rgb: 255, 255, 255; --background-start-rgb: 0, 0, 0; --background-end-rgb: 0, 0, 0; } } body { color: rgb(var(--foreground-rgb)); background: linear-gradient( to bottom, transparent, rgb(var(--background-end-rgb)) ) rgb(var(--background-start-rgb)); }

Now you can start your local development server by running the following terminal command:

npm run dev

This creates a local server on localhost:3000. Opening it on the browser looks like this:

hello-world
Your basic application running on localhost:3000

Like in most Next.js projects, you will build your website's components using JSX markup. In this case, you'll create components for different portions of your application matching your design system. Here's the final project, so you understand the design.

The more granular the components, the better control you afford your non-technical team in Uniform.

Start with building an essential layout component containing a header, footer, and hero component.

Create a folder called components in the /src directory, then add the following component files within src/components.

The components are inspired by TailwindUI Kit.

Header.tsx - A responsive navigation bar.

import { useState } from 'react' export function Header() { const [show, setShow] = useState(false) return ( <> <nav className="w-full border-b"> <div className="container mx-auto flex items-center justify-between px-6 py-5 md:py-0"> <div aria-label="Home. logo"> <h2 className="text-lg font-bold">Lyne</h2> </div> <div> <button onClick={() => setShow(!show)} className={`${ show ? 'hidden' : '' } text-gray-500 hover:text-gray-700 focus:text-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-500 sm:block md:hidden`} > <svg aria-haspopup="true" aria-label="open Main Menu" xmlns="http://www.w3.org/2000/svg" className="icon icon-tabler icon-tabler-menu md:hidden" width={24} height={24} viewBox="0 0 24 24" strokeWidth="1.5" stroke="#2c3e50" fill="none" strokeLinecap="round" > <path stroke="none" d="M0 0h24v24H0z" /> <line x1={4} y1={8} x2={20} y2={8} /> <line x1={4} y1={16} x2={20} y2={16} /> </svg> </button> <div id="menu" className={` ${show ? '' : 'hidden'} md:block lg:block `} > <button onClick={() => setShow(!show)} className={`fixed top-0 z-30 mt-6 block text-gray-500 hover:text-gray-700 focus:text-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-500 md:hidden lg:hidden`} > <svg aria-label="close main menu" xmlns="http://www.w3.org/2000/svg" width={24} height={24} viewBox="0 0 24 24" strokeWidth="1.5" stroke="#2c3e50" fill="none" strokeLinecap="round" strokeLinejoin="round" > <path stroke="none" d="M0 0h24v24H0z" /> <line x1={18} y1={6} x2={6} y2={18} /> <line x1={6} y1={6} x2={18} y2={18} /> </svg> </button> <ul className="fixed bottom-0 left-0 right-0 top-0 z-20 flex flex-col items-center justify-center bg-white py-10 text-3xl md:relative md:flex md:flex-row md:bg-transparent md:text-base"> <li className="cursor-pointer pt-10 text-base text-gray-700 hover:text-gray-900 md:pt-0 lg:text-lg"> <a href="#">Features</a> </li> <li className="cursor-pointer pt-10 text-base text-gray-700 hover:text-gray-900 md:ml-5 md:pt-0 lg:ml-10 lg:text-lg"> <a href="#">Pricing</a> </li> </ul> </div> </div> <button className="hidden rounded border border-[#082491] bg-transparent px-4 py-1 text-sm text-[#082491] transition duration-150 ease-in-out hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-[#082491] focus:ring-offset-2 sm:px-8 sm:py-3 md:block lg:text-lg lg:font-bold"> Sign in </button> </div> </nav> </> ) }

Footer.tsx - A responsive footer component.

import { useState } from 'react' export function Footer() { return ( <div> <footer id="footer" className="relative z-50 dark:bg-gray-50"> <div className="flex flex-col items-center justify-center py-5"> <p className="text-xs leading-none text-gray-900 lg:text-sm"> 2021 Lyne. All Rights Reserved. </p> </div> </footer> </div> ) }

Layout.tsx - Import and render the Header and Footer components.

import { Footer } from './Footer' import { Header } from './Header' export function Layout({ children }: { children: React.ReactNode }) { return ( <> <Header /> {children} <Footer /> </> ) }

Hero.tsx - A basic Hero component with a title, highlighted text, and subtitle.

type HeroPropType = { titleA: string titleB?: string highlightedText?: string subtext: string } export function Hero({ titleA, highlightedText, titleB, subtext, }: HeroPropType) { return ( <div className="overflow-y-hidden bg-gray-100 pb-12 lg:min-h-[700px]"> <> <div className="bg-gray-100"> <div className="container mx-auto flex flex-col items-center py-12 sm:py-24"> <div className="mb-5 w-11/12 flex-col items-center justify-center sm:mb-10 sm:w-2/3 lg:flex"> <h1 className="text-center text-2xl font-black leading-7 text-gray-800 sm:text-3xl md:text-4xl md:leading-10 lg:text-5xl xl:text-6xl"> {titleA} <span className="text-[#082491]"> {highlightedText} </span> {titleB} </h1> <p className="mt-5 text-center text-sm font-normal text-gray-400 sm:mt-10 sm:text-lg lg:w-10/12"> {subtext} </p> </div> </div> </div> </> </div> ) }

The components you create pass data using props. Uniform utilizes the same props to pass data into the components.

If you don't already have a Uniform account, you can request one at https://uniform.dev/try. You may already have a Uniform project in your dashboard. Either edit this or create a new project, Uniform plan permitting.

You'll start from your team dashboard.

  1. Navigate to Security > API keys.

  2. Click Add API key.

  3. Enter the following values:

    NameProjectRoles
    Getting started key[your project's name] (it may be auto populated with My first project)Developer (select from the dropdown)
  4. Click Create API key.

    create-api-key
    Create API key
  5. Copy the generated API key and project ID using Copy as .env and store them somewhere safe. You'll add these to your front-end application soon.

Uniform introduces the concepts of components and compositions. Components are building blocks of content on your website. They match your design system and are similar to components in React.js.

Compositions are a structured collection of components representing a portion of your website, in this case, a page. After creating a component, you'll use it in a composition and then render the composition on the front end.

components-compositions
Components within a composition

In the Uniform application, you'll create components to match those in your front-end application, except for the layout component. This is a reversible consideration and assumes you don't want users to modify the layout in Uniform.

  1. From your team dashboard, select a project and go to Experience > Components.
  2. Click either Create a Component (if this is your first component) or Add component.
  3. Enter the following values:
    FieldValue
    Component nameHero
    Public IDhero (this is auto generated based on the value in "component name")
    Component iconImage text
    Title ParameterNone
    Composition ComponentUnchecked
  4. Save the component by clicking the arrow in the button and selecting Save.

In Uniform, component parameters are fields for data. These map directly to component props in React.

  1. In the Parameters tab click Add parameter.

  2. Enter the following values:

    FieldValue
    Parameter NameTitle A
    Public IDtitleA (this is auto generated based on the value in "Parameter Name")
    Help Text(Optional. This appears next to the parameter in the Composition editor.)
    TypeText
    Required(Optional. True/False)
    Allow multi-line text(Optional. True/False)
    Validation Regex(Optional)
  3. Click OK.

    info

    In Uniform, the data in parameters can be of primitive types like Text or Number. However, they can also be data from external sources introduced through Uniform integrations. You'll learn more about this in subsequent tutorials.

    add-parameter
    The fields for adding a parameter
  4. Continue entering parameters until you have completed the following:

    NameTypePublic ID
    Title ATexttitleA
    Title BTexttitleB
    Highlighted TextTexthighlightedText
    SubtextTextsubtext
  5. Check that each parameter's public ID matches the corresponding prop in the <Hero/> component.

  6. Click Save and close.

create-component
The final, configured component.

To create a page, go back to the list of components and repeat the steps to create a component. Compositions must be configured as "Composition Components," which form the basis of compositions. The default project comes with a composition component called "Page.” If you deleted this component or started the project from scratch, create a new component called "Page” and check the "Composition Component" box on the setup page.

Add a text parameter called "Meta Title," make it required, and ignore all other options. This adds meta data to the page.

The page composition component uses slots to specify a list of components allowed as children on the page. This is similar to how React uses the children prop.

  1. Click on the Slots tab in the Page composition component

  2. Click Add slot

  3. Enter the following values:

    FieldValue
    Slot NameContent
    Public IDcontent (this is auto generated based on the value in "Slot Name")
    Required Component Quantity: Minimum
    Required Component Quantity: Maximum
    Allowed ComponentsAllow components and patters

    "Allowed components" allow you use all components in the Page composition component. The Required Component Quantity option specifies the minimum or maximum number of child components allowed in the slot.

    add-slot
    The configured slot
  4. Click OK.

    create-page
    Creating a composition component
  5. Save and close the component.

Now that you have configured your components you can use them to create a composition.

  1. Navigate to Experience > Compositions.

  2. Click Create a composition (if this is your first composition) or Add composition.

  3. Add the following values:

    FieldValue
    Select a composition typePage
    NameHome
    Slug/

    The default project created comes with a Home composition with a route of /. If you deleted the Home composition shipped with the default project, create a new composition called Home with a slug of /. Select Page as the composition type.

    create-composition
    A sample Canvas editor
  4. Click Create.

Once you create a composition you're taken to the Canvas visual editor.

The editor comprises the structure panel on the left, the preview panel in the middle, and the property panel on the right. If you aren't working with the default project, the preview won't be available yet.

sample-canvas-editor
The configuration screen for the composition
  1. Within the structure panel, delete any existing components including personalization on the parent "Page" component.

  2. Click the + under the Content slot to add the Hero component you created earlier as the first item on the page.

  3. In the property panel, enter the following sample content for each parameter:

    ParameterValue
    Title AThe Freedom to Create the
    Highlighted TextWebsites
    Title BYou Want
    SubtextEmpower your non-technical team to build and ship without touching a line of code. Focus on what matters as a developer.
  4. Save and publish the composition.

    info

    Always publish compositions after an update to surface the changes in the frontend.

There are multiple ways to pull data from a Uniform composition into your website. This tutorial fetches the composition by its slug.

  1. In your Next.js project, create a .env file in the root of your project to hold all environment variables and secrets. Add the Uniform API Key and Project ID using the variables:

    UNIFORM_API_KEY=xxxxxxxxxxxxx UNIFORM_PROJECT_ID=xxxxxxxxxxxxx

    Add the .env file to the .gitignore file, excluding it from version control.

  2. Install Uniform Canvas packages with the following command:

    npm install @uniformdev/canvas @uniformdev/canvas-react @uniformdev/canvas-next
  3. In the src/pages/index.tsx file, enable fetching the Uniform composition with a slug matching the current Next.js route and providing it as props to the route component. This should be the first line:

    src/pages/index.tsx

    export { getServerSideProps } from '@uniformdev/canvas-next/slug'

    About this step

    In the block above, you configured Next.js to use the default slug-based composition route resolver and server-side rendering. It's also possible to:

    • Use static rendering (getStaticProps/getStaticPaths exports)

    • Customize the settings or extend the props function, such as fetching only draft compositions:

      export const getServerSideProps = withUniformGetServerSideProps({ requestOptions: { state: CANVAS_DRAFT_STATE } })
    • Use alternative route resolution algorithms such as project map (use @uniformdev/canvas-next/project-map instead of @uniformdev/canvas-next/slug in your import)

  4. Next, in the index.tsx file, you'll update the imports with the required modules and render the Composition using the code below:

    /index.tsx

    export { getServerSideProps } from '@uniformdev/canvas-next/slug' import { Layout } from '@/components/Layout' import Head from 'next/head' import { UniformComposition, UniformSlot } from '@uniformdev/canvas-react' import { RootComponentInstance } from '@uniformdev/canvas' export default function Home(props: { data: RootComponentInstance }) { return ( <> <Head> <title>Lyne | Home</title> <meta name="viewport" content="width=device-width, initial-scale=1" /> </Head> <Layout> {/* Renders the components of your Uniform Composition resolved by getServerSideProps */} <UniformComposition data={props.data}> <UniformSlot name="content" /> </UniformComposition> </Layout> </> ) }

    About this step

    The UniformComposition component wraps the composition components and receives the composition data as a prop. <UniformSlot/> renders all components added to the slot named content in Uniform. This is the Page composition's content slot's public id.

  5. The last step to visualize the composition is to register your React component with the Uniform SDK so that it knows what to render when it receives a named component.

    In components/Hero.tsx, register it with the Uniform SDK by adding this at the bottom:

    Hero.tsx

    import { registerUniformComponent } from "@uniformdev/canvas-react"; // rest of the component as-is registerUniformComponent({ type: "hero", component: Hero });

    Then import your Hero component file into index.tsx so that the registration becomes visible to the <UniformComposition> component:

    pages/index.tsx

    import '@/components/Hero';

    You have now mapped the Hero component to any Uniform component with the public ID of hero.

You should now have a page with a Layout, Hero section, and content coming from Uniform.

sample-output
Your sample page

With this, you've covered the basics of setting up a Uniform project and connecting it to your Next.js project.

To activate the preview panel in Uniform, you'll create a Next.js API route containing the preview logic. Uniform ships a module to set this up.

  1. In your project, install the Uniform Canvas Next.js package using the command:

    npm install @uniformdev/canvas-next

    info

    Learn more about the @uniformdev/canvas-next package.

  2. In src/pages/api create a new file called "preview.ts" with the following:

    preview.ts

    import { createPreviewHandler } from '@uniformdev/canvas-next' const handler = createPreviewHandler({ secret: () => process.env.UNIFORM_PREVIEW_SECRET, }) export default handler

    The code above uses the createPreviewHandler method to set up a preview with contextual editing supported.

  3. Add the secret as an environment variable in your .env file with the following:

    UNIFORM_PREVIEW_SECRET=uniform
  4. Restart your development server, and your preview URL will become: http://localhost:3000/api/preview?secret=uniform.

    warning

    Visiting this link in the browser will result in JSON error "{"message":"Couldn't resolve the full path of the preview page"}"

  5. Copy this URL, and in Uniform, navigate to your project and go to: Settings > Canvas Settings and update the URL under Preview Configuration with your new preview URL.

  6. Save the preview URL.

  7. Navigate to your Home Composition in Uniform, and you should have the preview active, similar to the image below.

    preview-image
    The Uniform contextual editor showing your application content

Summary

Continue building the page by:

  1. Adding more front-end components. Here are sample Features, FeatureCard, CTA, and CTA button components.
  2. Registering the components in the resolveRenderer() function.
  3. Modeling the components on Uniform.
  4. Adding components and content to the composition in Uniform.
  5. Repeat the process above for new pages. Import and reuse functions wherever possible.

Here's the project repository and a deployed version.