Building an Email-Protected Notion Page Using Next.js and React-Notion-X
Tags: nextjs, webdev, tutorial, opensource
Published: 2024-01-03 12:17:59

## What you will find in this article?

In the era of information sharing, protecting sensitive content is crucial. Notion has become a popular tool for content management, but what if you need to restrict access?

Email protection is a straightforward yet effective way to control who accesses your content. By requiring visitors to enter their email, you not only restrict access but also gather potential leads or feedback.

In this tutorial, we'll walk through building an email-protected Notion page using Next.js and `react-notion-x`.


![no noobs allowed](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5okcw713chp796jw55cy.gif)


## Papermark - the open-source DocSend alternative.

Before we kick it off, let me share Papermark with you. It's an open-source alternative to DocSend that helps you securely share documents from PDFs to Notion pages and get real-time page-by-page analytics from viewers. It's all open-source!

I would be grateful if you could give us a star! Don't forget to share your thoughts in the comments section ❤️
[https://github.com/mfts/papermark](https://github.com/mfts/papermark)

[![Papermark App](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/81jinvhu0qa5rxgbws56.gif)](https://github.com/mfts/papermark)


## Setup the project

Let's go ahead and set up our project environment for our Notion Lead Generator application. We'll be creating a Next.js app, react-notion-x and Tailwind CSS.

### Setting up Next.js with TypeScript and Tailwindcss

We'll use `create-next-app` to generate a new Next.js project. We'll also be using TypeScript and Tailwind CSS, so make sure to select those options when prompted.

```bash
npx create-next-app

# ---
# you'll be asked the following prompts
What is your project named?  my-app
Would you like to add TypeScript with this project?  Y/N
# select `Y` for typescript
Would you like to use ESLint with this project?  Y/N
# select `Y` for ESLint
Would you like to use Tailwind CSS with this project? Y/N
# select `Y` for Tailwind CSS
Would you like to use the `src/ directory` with this project? Y/N
# select `N` for `src/` directory
What import alias would you like configured? `@/*`
# enter `@/*` for import alias
```

### Setting up react-notion-x

Next, we'll install `react-notion-x` to fetch our Notion page data. `notion-client`, `notion-utils`, and `notion-types` are peer dependencies and contain helper methods for `react-notion-x`, so we'll install those as well.

```bash
npm install react-notion-x notion-client notion-utils notion-types
```

## Building the application

Now that we have our setup in place, we are ready to start building our application. The main features we'll cover are:

- Render Notion page content in React
- Add email capture form to the page

### #1 Render Notion page content in React

Let's create a component that contains the Notion page content. We'll create a new component `components/notion-page.tsx` and add the following code to it.

```tsx
// components/notion-page.tsx
import { ExtendedRecordMap } from "notion-types";
import { NotionRenderer } from "react-notion-x";
// core styles shared by all of react-notion-x (required)
import "react-notion-x/src/styles.css";

export const NotionPage = ({ recordMap }: { recordMap: ExtendedRecordMap }) => {
  if (!recordMap) {
    return null;
  }

  return (
    <div className="bg-white">
      <NotionRenderer
        recordMap={recordMap}
        fullPage={true}
        darkMode={false}
        disableHeader={true}
      />
    </div>
  );
};
```

```tsx
// pages/index.tsx
import { NotionAPI } from "notion-client";
import { type ExtendedRecordMap } from "notion-types";
import { parsePageId } from "notion-utils";
import { type GetStaticPropsContext } from "next";

const notion = new NotionAPI();

export const getStaticProps = async (context: GetStaticPropsContext) => {
  const notionUrl = "https://www.notion.so/..."; // enter your Notion page URL here

  let pageId = null;
  let recordMap = null;

  const notionPageId = parsePageId(file, { uuid: false });
  if (!notionPageId) {
    return {
      notFound: true,
    };
  }

  pageId = notionPageId;
  recordMap = await notion.getPage(pageId);

  return {
    props: {
      notionData: {
        recordMap,
      },
    },
  };
};

export async function getStaticPaths() {
  return {
    paths: [],
    fallback: true,
  };
}

export default function Page({
  notionData,
}: {
  notionData: {
    recordMap: ExtendedRecordMap | null;
  };
}) {
  return <NotionPage notionData={notionData} />;
}
```

![Rendered Notion page content](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mjyrvnfue3rgtdpj2wgf.png)

### #2 Add email capture form to the page

Now that we have our Notion page content, we can add an email capture form to the page. We'll create a new component `components/email-capture.tsx` and add the following code to it.

```tsx
// components/email-capture.tsx
import { useEffect } from "react";
import { Button } from "@/components/ui/button";

export default function EmailCapture({
  email,
  setEmail,
  onSubmitHandler,
  isLoading,
}: {
  email: string;
  setEmail: React.Dispatch<React.SetStateAction<string>>;
  onSubmitHandler: React.FormEventHandler<HTMLFormElement>;
  isLoading: boolean;
}) {
  return (
    <>
      <div className="flex h-screen flex-1 flex-col  px-6 py-12 lg:px-8 bg-black">
        <div className="sm:mx-auto sm:w-full sm:max-w-md">
          <h2 className="mt-10 text-2xl font-bold leading-9 tracking-tight text-white">
            Your action is requested to continue
          </h2>
        </div>

        <div className="mt-10 sm:mx-auto sm:w-full sm:max-w-md">
          <form className="space-y-4" onSubmit={onSubmitHandler}>
            <div className="pb-5">
              <div className="relative rounded-md shadow-sm space-y-2">
                <label
                  htmlFor="email"
                  className="block text-sm font-medium leading-6 text-white">
                  Email address
                </label>
                <input
                  name="email"
                  id="email"
                  type="email"
                  autoComplete="email"
                  className="flex w-full rounded-md border-0 py-1.5 text-white bg-black shadow-sm ring-1 ring-inset ring-gray-600 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-gray-300 sm:text-sm sm:leading-6"
                  placeholder="Enter email"
                  onChange={(e) => {
                    setEmail(e.target.value);
                  }}
                  aria-invalid="true"
                />
                <p className="text-sm text-gray-600">
                  This data will be shared with the owner of this Notion page.
                </p>
              </div>
            </div>

            <div className="flex justify-center">
              <Button type="submit" className="w-1/3" loading={isLoading}>
                Continue
              </Button>
            </div>
          </form>
        </div>
      </div>
    </>
  );
}
```

```tsx
// pages/index.tsx
// ...

export default function Page({
  notionData,
}: {
  notionData: {
    recordMap: ExtendedRecordMap | null;
  };
}) {
  const [email, setEmail] = useState<string>("");
  const [submitted, setSubmitted] = useState<boolean>(false);
  const [isLoading, setIsLoading] = useState<boolean>(false);

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    setIsLoading(true);
    const res = await fetch(`< API_ENDPOINT >`, {
      method: "POST",
      body: JSON.stringify({ email }),
    });
    setSubmitted(true);
    setIsLoading(false);
  };

  // If email is not submitted, show the access form
  if (!submitted) {
    return (
      <EmailCapture
        email={email}
        setEmail={setEmail}
        onSubmitHandler={handleSubmit}
        isLoading={isLoading}
      />
    );
  }

  return (
    <div className="bg-gray-950">
      {submitted && notionData?.recordMap ? (
        <NotionPage recordMap={notionData.recordMap} />
      ) : null}
    </div>
  );
}
```

![Email Capture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g4t2vq8rxvuy1x1etnra.png)

_Caveat_: `react-notion-x` works only with publicly accessible Notion pages. However, don't fret because the Notion page content is SSR'd on the server, so it's not publicly accessible and there's no place where the public Notion id is exposed in the client.

## Conclusion

Amazing! You can now create an email-protected Notion page using Next.js and `react-notion-x`. Start sharing your content with the world and collect leads and feedback from real people!

Thank you for reading. I am Marc, an open-source advocate. I am building [papermark.io](https://www.papermark.io) - the open-source alternative to DocSend.

Have fun building!

## Help me out!

If you found this article helpful and got to understand react-notion-x, Next.js and SSR, I would be extremely glad if you could give us a star! And don't forget to share your thoughts in the comments ❤️

[https://github.com/mfts/papermark](https://github.com/mfts/papermark)


![cat thanks](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c5gqdzyca5czincireq2.gif)