Tutorial: SSX and Next.js

Next.js provides developers with the right tools to create their latest projects. This guide will show you how to install Sign-In with Ethereum in your Next.js app by using SSX.

Tutorial: SSX and Next.js

Next.js has emerged as a trailblazing framework for building decentralized applications, owing to its robust architecture and versatile features. Next.js provides developers with the right tools to create their latest project, and it can be very simple to install Sign-In with Ethereum in your Next.js app by using SSX.

This guide will show you how to set it up in just a few steps:

  1. Creating your Next.js project - you can skip this step if you already have one
  2. Adding SSX dependencies - how to install the dependencies on your project
  3. Setting up the Backend - how to configure your backend
  4. Setting up the Frontend - how to configure your frontend

So, let’s dive in.

1) Creating a Next.js project

Note: if you already have a Next app, you can skip to the next section.

First, create a new Next app by running this command:

npx create-next-app@latest

In this tutorial, we are using a project with the following configuration:

✔ What is your project named? … my-ssx-dapp
✔ Would you like to use TypeScript with this project? … Yes
✔ Would you like to use ESLint with this project? … Yes
✔ Would you like to use Tailwind CSS with this project? … No
✔ Would you like to use `src/` directory with this project? … No
✔ Would you like to use experimental `app/` directory with this project? … No
✔ What import alias would you like configured? … @/*

2) Adding SSX dependencies

To use the SSX library, you must install the @spruceid/ssx dependency for the frontend, and @spruceid/ssx-server for the backend. To add it, navigate to your app’s directory and use the following command like so:

cd my-ssx-dapp
npm install @spruceid/ssx @spruceid/ssx-server


3) Setting up the Backend

Let's start by adding a helper file on our code so that we don't have to instantiate a new SSX Server object directly every time. Create the file pages/api/_ssx.ts and add the following:

import { SSXServer } from "@spruceid/ssx-server";

const ssx = new SSXServer();

export default ssx;

For more information about configuring SSX Server, you can check out our documentation.

Second, create an API route for generating a random nonce. This is used to identify the session and prevent replay attacks. Create the file pages/api/ssx-nonce.ts and add the following:

import { NextApiRequest, NextApiResponse } from "next";
import ssx from "./_ssx";

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  const nonce = ssx.generateNonce();
  req.cookies.nonce = nonce;
  res.status(200).send(nonce);
}

Third, add a sign-in API route. This route will do all necessary checks in the SIWE message, ensuring it's valid. All checks occur inside the ssx.login() function. Create the file pages/api/ssx-login.ts and add the following:

import { NextApiRequest, NextApiResponse } from "next";
import ssx from "./_ssx";

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  res.status(200).json(
    await ssx.login(
      req.body.siwe,
      req.body.signature,
      req.body.daoLogin,
      req.body.resolveEns,
      req.cookies.nonce || "",
      req.body.resolveLens,
    )
  );
}

Finally, create the sign-out API route. Create the file pages/api/ssx-logout.ts and add the following:

import { NextApiRequest, NextApiResponse } from "next";
import ssx from "./_ssx";

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  res.status(200).json({ 
    success: await ssx.logout() ?? true
  });
}


You can find more information about ssx.generateNonce, ssx.login, and ssx.logout in the SSX documentation.

4) Setting up the Frontend

First, we will create a new SSXComponent to wrap the sign-in/out logic. Create a new folder called components in the root directory, create the file components/SSXComponent.tsx, and add the following to it:

import { SSX } from "@spruceid/ssx";
import { useState } from "react";

const SSXComponent = () => {

    const [ssxProvider, setSSX] = useState<SSX | null>(null);

    const ssxHandler = async () => {
        const ssx = new SSX({
            providers: {
                server: {
                    host: "http://localhost:3000/api"
                }
            },
        });
        await ssx.signIn();
        setSSX(ssx);
    };

    const ssxLogoutHandler = async () => {
        ssxProvider?.signOut();
        setSSX(null);
    };

    const address = ssxProvider?.address() || '';

    return (
        <div className="App">
            <div className="App-header">
                <h1>&nbsp;SSX Example dapp</h1>
            </div>
            {
                ssxProvider ?
                    <div className="App-content">
                        <h2>
                            Account Info
                        </h2>
                        <div>
                            {
                                address &&
                                <p>
                                    <b>Address:</b> <code>{address}</code>
                                </p>
                            }
                        </div>
                        <button onClick={ssxLogoutHandler}>
                            Sign-Out
                        </button>
                    </div> :
                    <div className="App-content">
                        <h2>
                            Connect and Sign-In with your Ethereum account.
                        </h2>
                        <button onClick={ssxHandler}>
                            Sign-In with Ethereum
                        </button>
                    </div>
            }
        </div>
    );
};

export default SSXComponent;

This SSX configuration assumes you are running the Next.js dapp using the default port (3000). If you are running in a different port, change the providers.server.host in the SSX config. For more information about SSX client configuration, you can check out our documentation.

Finally, let’s update the pages/index.tsx file with the following code. Add the additional imports specified, and if you generated your app using the create-next-app tool, replace the existing export default function Home() {} with the following:

import SSXComponent from '@/components/SSXComponent';
import type { NextPage } from 'next'
import Head from 'next/head'


const Home: NextPage = () => {

  return (
    <>
      <Head>
        <title>SSX Next Dapp</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <SSXComponent />
    </>
  )
}

export default Home;

That's it! Now you can run the dapp by using yarn dev or npm run dev.

If you want to add some style to your new dapp, add the following to your styles/globals.css, but this step is completely optional:

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}

.App {
  text-align: center;
  background-color: #212121;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: white;
}

.App-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  margin: 24px auto;
}

.App-logo {
  height: 40px;
  pointer-events: none;
  margin-right: 16px;
}

.App-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}


h1 {
  font-size: 32px;
  line-height: 48px;
}

p {
  font-size: 24px;
  line-height: 36px;
}

button {
  padding: 16px 24px;
  margin: 8px auto;
  font-weight: 700;
  color: white;
  background-color: transparent;
  border: 1px solid white;
  border-radius: 24px;
  cursor: pointer;
  transition: all 150ms ease 0s;
}

button:disabled {
  pointer-events: none;
  opacity: .7;
}

button:hover {
  color: #212121;
  background-color: white;
  transform: scale(1.05);
}

And that's all there is to it!

We want to support any developer looking to integrate Sign-In with Ethereum, no matter the requirements. As we continue to build out SSX, we invite you to request any features or examples you want. Alternatively, we also welcome all open-source contributions to SSX from developers.


Spruce is building a future where users control their identity and data across all digital interactions. If you're curious about integrating Spruce's open-source libraries, such as Sign-In with Ethereum or SSX (as described here) into your project, chat with us in Discord or check out our libraries on Github.