/react-notion-render

A library to render notion pages

Primary LanguageTypeScript

React Notion Render

A library to render notion pages


NPM npm PR Stars

Table of contents

Description

When we want to retrieve the content of a Notion page, using the Notion API we will obtain a complex block structure(like this example). This package solves that structure and takes care of rendering that response.

Installation

npm i @9gustin/react-notion-render

Examples

Basic example

I would use the package @notionhq/client to get data from the Notion API and take this example of Notion Service also you can fetch the data from the api. This example take pages of an database an render the first of list. This example is an Page in Next.js.

import { Render } from '@9gustin/react-notion-render'
import { getBlocks, getDatabase } from '../services/notion'

export default ({blocks}) => <Render blocks={blocks} />

export const getStaticProps = async () => {
  const DATABASE_ID = '54d0ff3097694ad08bd21932d598b93d'
  const database = await getDatabase(DATABASE_ID)
  const blocks = await getBlocks(database[0].id)

  return {
    props: {
      blocks
    }
  }
}

Blog with Notion as CMS

I've maded a template to blog page, that use this package and allows you have a blog using notion as CMS.

📎 Repo: @9gustin/notion-blog-nextjs
📚 Notion Database: notion/notion-blog-nextjs
✨Web: blog-template

Note: My personal blog now it's using this template. Url: 9gustin.com

Notion page to single page

This example it's not maded by me, but i show you what package can do. This is a single page which use this package to render content
📎 Repo: sasigume/notion-to-next-single-page

Usage

Giving styles

If you followed the basic example, tou take count that the page are rendered without styles, only pure text. To solve that we can use the Render props, like the following cases

Using default styles

This package give you default styles, colors, text styles(blod, italic) and some little things, if you want use have to add two things:

First import the stylesheet

import '@9gustin/react-notion-render/dist/index.css'

And then add to the Render the prop useStyles, like that:

<Render blocks={blocks} useStyles />

And it's all, now the page looks some better, i tried to not manipulate that styles so much to preserve generic styles.

Using your own styles

If you want to add styles by your own, you can use the prop classNames, this props gives classes to the elements, it make more easier to identify them. For example to paragraphs give the class "rnr-paragraph", and you can add this class in your CSS and give styles.

<Render blocks={blocks} classNames />

This is independient to the prop useStyles, you can combinate them or use separated.

Components Reference

ClassName Notion Reference HTML Tag
rnr-heading_1 Heading 1 h1
rnr-heading_2 Heading 2 h2
rnr-heading_3 Heading 3 h3
rnr-paragraph Paragraph p
rnr-to_do To-do List ul
rnr-bulleted_list_item Bulleted List ul
rnr-numbered_list_item Numered List ol
rnr-toggle Toggle List ul

Text Styles

ClassName Notion Reference
rnr-bold Bold
rnr-italic Italicize
rnr-strikethrough Strike Through
rnr-underline Underline

Text colors

ClassName HEX
rnr-red #ff2525
rnr-gray #979797
rnr-brown #816868
rnr-orange #FE9920
rnr-yellow #F1DB4B
rnr-green #22ae65
rnr-purple #a842ec
rnr-pink #FE5D9F
rnr-blue #0eb7e4

...moreProps

The Render component has two more props that you can use.

Custom title url

With this package you can pin the titles in the url to share it. For example, if you have a title like My Title and you click it, the url looks like url.com#my-title. The function that parse the text it's here, you can check it. But if you want some diferent conversion you can pass a custom slugify function. In case that you want to separate characthers by _ instead of - yo can pass the slugifyFn prop:

<Render blocks={blocks} slugifyFn={text => text.replace(/[^a-zA-Z0-9]/g,'_')} />

Or whatever you want, slugifyFn should receive and return a string.

Preserve empty blocks

Now by default the Render component discard the empty blocks that you put in your notion page. If you want to preserve you can pass the prop emptyBlocks and it be rendered.

<Render blocks={blocks} emptyBlocks />

The empty blocks contain the class "rnr-empty-block", this class has default styles (with useStyles) but you can apply your own styles.

Custom components

Now Notion API only supports text blocks, like h1, h2, h3, paragraph, lists(Notion Doc.). Custom components are here for you, it allows you to use other important blocks.

Important
The text to custom components sould be plain text, when you paste a link in Notion he convert to a link. You should convert it to plain text with the "Remove link" button. Like there: image

Link

Now you can use links like Markdown, links are supported by Notion API, but this add the possibility to made autorreference links, as an index.

Example:

Index:
[1. Declarative](#declarative)
[2. Component Based](#component-based)
[3. About React](#about-react)

The link be maded with the slugifyFn, you can check the default, or pass a custom.

Image

This it simple, allows you to use images(includes GIF's). The sintax are the same like Markdown images. For it you have to include next text into your notion page as simple text

Example:

![My github profile pic](https://avatars.githubusercontent.com/u/38046239)

Plus
Also you can add a link to image, like an image anchor. This link would be opened when the user click the image. Thats works adding an # with the link after the markdown image.

![My github profile pic](https://avatars.githubusercontent.com/u/38046239)#https://github.com/9gustin

So when the user click my image in the blog it will be redirected to my github profile.

Video

You can embed Videos. You have 3 ways to embed a video.

  • Local
  • Youtube
  • Google Drive
Player Description Syntax Example
Local Search in public folder -[title](url) -[Local](/loremVideo.mp4)
Youtube Youtube reproductor with share url -[title](url)#youtube -[Youtube](https://youtu.be/aA7si7AmPkY)#youtube
Google Drive Google drive reproductor with share url -[title](url)#googleDrive -[GoogleDrive](https://drive.google.com/file/d/1BmIxtck_9FuMfZOKfJDQK_WvIl8cDV11/view?usp=sharing)#googleDrive

How can i get Youtube video share url?:
youtubeStep1 youtubeStep2

How can i get Google Drive video share url?:
Open the video
image
Click the 3 points icon then click Share
image
Copy the ilnk
image

Display the table of contents

Now we exporting the indexGenerator function, with that you can show a table of contents of your page content. This function receive a list of blocks and return only the title blocks. The structure of the result it's like:

image

you can use it like that:

import { indexGenerator, rnrSlugify } from '@9gustin/react-notion-render'

const TableOfContents = ({blocks}) => {
  return (
    <>
      Table of contents:
      <ul>
        {
          indexGenerator(blocks).map(({ id, plainText, type }) => (
            <li key={id}>
              <a href={`#${rnrSlugify(plainText)}`}>
                {plainText} - {type}
              </a>
            </li>
          ))
        }
      </ul>
    </>
  )
}

export default TableOfContents

if you want to add links use rnrSlugify or your custom slugify function to generate the href.

Migrating from v2 to v3

In v2 we use render as a function (we have render, renderBlocks and renderTitle). So when use it should use like that:

import { renderBlocks, renderTitle } from '@9gustin/react-notion-render'

const MyComponent = ({blocks, titleBlock}) => {
  return (
    <div>
      ...some stuff
      <h1>
        {renderTitle(titleBlock)}
      </h1>  
      {renderBlocks(blocks)}
    </div>
  )
}

Now we do like that:

import { Render } from '@9gustin/react-notion-render'

const MyComponent = ({blocks, titleBlock}) => {
  return (
    <div>
      ...some stuff
      <h1>
        <Render blocks={[titleBlock]} />
      </h1>
      <Render blocks={blocks} useStyles />
    </div>
  )
}

Now the Render component supports page content blocks and title blocks.
The concept of the Render are that receive blocks and parse it to React.ReactNode(or Elements), and in there use React Hooks. So it was a function but only can be used into components. And then it corresponds to be a Component.

Contributions:

If you find a bug, or want to suggest a feature you can create a New Issue and will be analized. Contributions of any kind welcome!

License

MIT © 9gustin