DEV Community

Cover image for Building an Email-Protected Notion Page Using Next.js and React-Notion-X
Marc Seitz for Papermark

Posted on

Building an Email-Protected Notion Page Using Next.js and React-Notion-X

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

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

Papermark App

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.

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
Enter fullscreen mode Exit fullscreen mode

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.

npm install react-notion-x notion-client notion-utils notion-types
Enter fullscreen mode Exit fullscreen mode

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.

// 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>
  );
};
Enter fullscreen mode Exit fullscreen mode
// 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} />;
}
Enter fullscreen mode Exit fullscreen mode

Rendered Notion page content

#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.

// 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>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode
// 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>
  );
}
Enter fullscreen mode Exit fullscreen mode

Email Capture

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 - 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

cat thanks

Top comments (8)

Collapse
 
srbhr profile image
Saurabh Rai

As a notion fan, I'd give this one a try!

Collapse
 
mfts profile image
Marc Seitz

same! notion is amazing :)

Collapse
 
fernandezbaptiste profile image
Bap

Wouuuh, I might just give it a try! Thanks a lot 🙏

Collapse
 
mfts profile image
Marc Seitz

Glad you find it useful 🙌

Collapse
 
shnai0 profile image
Iuliia Shnai

Papermark to the 🌙

Collapse
 
mfts profile image
Marc Seitz

To the 🌑

Collapse
 
nevodavid profile image
Nevo David

Great article!

Collapse
 
mfts profile image
Marc Seitz

Thanks Nevo 🔥