NotionX / react-notion-x

Fast and accurate React renderer for Notion. TS batteries included. ⚡️
https://react-notion-x-demo.transitivebullsh.it
MIT License
4.69k stars 544 forks source link

Next.js App Router Customer Image Component #549

Open vader1359 opened 3 months ago

vader1359 commented 3 months ago

CleanShot 2024-04-17 at 09 13 15 I switch to AppRouter and using the "use client" for the NotionPage. The page rendered correctly but no matter what I try, the images are not optimized. I both tried using the next/image, passing nextImage to the custom component prop I also create a Cloudinary Image Component and pass to the customer component prop It does not work.

Anyone have the similar issue?

Test Notion Page: https://www.notion.so/iant1359/About-Us-ea21588ce29b43038e42c7979d715b7d

My code:


// core styles shared by all of react-notion-x (required)
import 'react-notion-x/src/styles.css'

import * as React from 'react'
import dynamic from 'next/dynamic'
import Head from 'next/head'
import Image from 'next/image'
import Link from 'next/link'
// import { useRouter } from 'next/router'

import { ExtendedRecordMap } from 'notion-types'
import { getPageTitle } from 'notion-utils'
import { NotionRenderer } from 'react-notion-x'

import {CldImageProps} from "next-cloudinary"
import CldImage from './CldImage'

// import TweetEmbed from 'react-tweet-embed'

// -----------------------------------------------------------------------------
// dynamic imports for optional components
// -----------------------------------------------------------------------------

const Collection = dynamic(() =>
  import('react-notion-x/build/third-party/collection').then(m => m.Collection),
)

// Extend the props for your ImageComponent
interface ImageComponentProps extends CldImageProps {
  // Add any additional props here if needed
}

const ImageComponent: React.FC<ImageComponentProps> = (props) => (
  <CldImage
    {...props}
    width={2048}
    height={1600}
    alt="illustration image for doc"
    deliveryType="fetch"
    dpr="auto"
    config={{ cloud: { cloudName: 'iant1359' } }}
  />
);

 const NotionPage = ({
  recordMap,
  previewImagesEnabled,
  rootPageId,
  rootDomain,
}: {
  recordMap: ExtendedRecordMap
  previewImagesEnabled?: boolean
  rootPageId?: string
  rootDomain?: string
}) => {
  // const router = useRouter()

  // if (router.isFallback) {
  //   return <Loading />
  // }

  if (!recordMap) {
    return null
  }

  const title = getPageTitle(recordMap)

  // useful for debugging from the dev console
  if (typeof window !== 'undefined') {
    const keys = Object.keys(recordMap?.block || {})
    const block = recordMap?.block?.[keys[0]]?.value
    const g = window as any
    g.recordMap = recordMap
    g.block = block
  }

  const socialDescription = 'React Notion X Demo'
  const socialImage =
    'https://react-notion-x-demo.transitivebullsh.it/social.jpg'

  return (
    <>
      <Head>
        {socialDescription && (
          <>
            <meta name="description" content={socialDescription} />
            <meta property="og:description" content={socialDescription} />
            <meta name="twitter:description" content={socialDescription} />
          </>
        )}

        {socialImage ? (
          <>
            <meta name="twitter:card" content="summary_large_image" />
            <meta name="twitter:image" content={socialImage} />
            <meta property="og:image" content={socialImage} />
          </>
        ) : (
          <meta name="twitter:card" content="summary" />
        )}

        <title>{title}</title>
        <meta property="og:title" content={title} />
        <meta name="twitter:title" content={title} />
        <meta name="twitter:creator" content="@transitive_bs" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <NotionRenderer
        recordMap={recordMap}
        fullPage
        darkMode={false}
        rootDomain={rootDomain}
        rootPageId={rootPageId}
        previewImages={previewImagesEnabled}
        components={{
          // NOTE (transitive-bullshit 3/12/2023): I'm disabling next/image for this repo for now because the amount of traffic started costing me hundreds of dollars a month in Vercel image optimization costs. I'll probably re-enable it in the future if I can find a better solution.
          nextImage: ImageComponent,
          nextLink: Link,
          // Collection,
        }}

        // NOTE: custom images will only take effect if previewImages is true and
        // if the image has a valid preview image defined in recordMap.preview_images[src]
      />
    </>
  )
}

export default NotionPage
import { ExtendedRecordMap, SearchParams, SearchResults } from 'notion-types';

import { previewImagesEnabled } from './config';

// Type definition for the image map (if not already defined)
interface PreviewImageMap {
    [key: string]: string;
}

async function getPreviewImageMap(recordMap: ExtendedRecordMap): Promise<PreviewImageMap> {
    const previewImageMap: PreviewImageMap = {};
    const blocks = recordMap.block;

    if (blocks) {
        Object.keys(blocks).forEach(blockId => {
            const block = blocks[blockId].value;
            if (block && block.type === 'image' && block.properties && block.properties.source) {
                const imageUrl = block.properties.source[0][0] as string;  // Assuming source is stored this way
                previewImageMap[blockId] = transformImageURLToPreview(imageUrl);
            }
        });
    }

    return previewImageMap;
}

const notion = new NotionAPI();

export async function getPage(pageId: string): Promise<ExtendedRecordMap> {
    const recordMap = await notion.getPage(pageId);

    if (!recordMap) {
      throw new Error('Failed to retrieve the page data from Notion');
    }

    if (previewImagesEnabled && recordMap) {
      try {
        const previewImageMap = await getPreviewImageMap(recordMap);
        (recordMap as any).preview_images = previewImageMap;
      } catch (error) {
        console.error('Error fetching preview images:', error);
        throw new Error('Error processing preview images');
      }
    }

    console.log(recordMap)

    return recordMap;
}

export async function search(params: SearchParams): Promise<SearchResults> {
    return notion.search(params);
}

function transformImageURLToPreview(url: string): string {
    return url.replace('original', 'preview');
}
4anghyeon commented 2 weeks ago

this way worked for me.

function NextImageComponent(props: ImageProps) {
  return (
    <Image
      {...props}
      width={400}
      height={400}
      // loading={"eager"}
    ></Image>
  );
}

function NotionContainer() {
  return (
    <NotionRenderer
      ...
      forceCustomImages={true}
      components={{
        Image: NextImageComponent,
        nextLink: Link,
      }}
    />
  );
}