Merge pull request #641 from transitive-bullshit/feature/maintenance-fall-2024

This commit is contained in:
Travis Fischer
2024-11-01 00:42:21 -05:00
committed by GitHub
52 changed files with 5459 additions and 4179 deletions

View File

@@ -1,27 +1,17 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "react", "react-hooks"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"prettier"
],
"settings": {
"react": {
"version": "detect"
}
},
"env": {
"browser": true,
"node": true
},
"extends": ["@fisch0920/eslint-config"],
"rules": {
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/no-non-null-assertion": 0,
"@typescript-eslint/no-unused-vars": 2,
"react/prop-types": 0
"react/prop-types": "off",
"unicorn/no-array-reduce": "off",
"unicorn/filename-case": "off",
"no-process-env": "off",
"array-callback-return": "off",
"jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/no-static-element-interactions": "off",
"jsx-a11y/media-has-caption": "off",
"jsx-a11y/interactive-supports-focus": "off",
"jsx-a11y/anchor-is-valid": "off",
"@typescript-eslint/naming-convention": "off"
}
}

View File

@@ -1,22 +1,44 @@
name: Build
name: CI
on: [push, pull_request]
jobs:
Build:
test:
name: Test Node.js ${{ matrix.node-version }}
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
node-version:
- 18
- 22
steps:
- uses: actions/checkout@v3
- name: Checkout
uses: actions/checkout@v4
- uses: actions/setup-node@v3
- name: Install pnpm
uses: pnpm/action-setup@v3
id: pnpm-install
with:
node-version: 16
cache: yarn
version: 9.12.2
run_install: false
- run: yarn install --frozen-lockfile
- name: build
# TODO Enable those lines below if you use a Redis cache, you'll also need to configure GitHub Repository Secrets
# env:
# REDIS_HOST: ${{ secrets.REDIS_HOST }}
# REDIS_PASSWORD: ${{ secrets.REDIS_PASSWORD }}
run: yarn build
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile --strict-peer-dependencies
- name: Run test
run: pnpm test
# TODO Enable those lines below if you use a Redis cache, you'll also need to configure GitHub Repository Secrets
# env:
# REDIS_HOST: ${{ secrets.REDIS_HOST }}
# REDIS_PASSWORD: ${{ secrets.REDIS_PASSWORD }}
# - name: Build
# run: pnpm build

View File

@@ -7,14 +7,5 @@
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "always",
"trailingComma": "none",
"importOrder": [
"^(react/(.*)$)|^(react$)|^(next/(.*)$)|^(next$)",
"<THIRD_PARTY_MODULES>",
"^(@/lib/(.*)$)|^(@/components/(.*)$)|^(@/styles/(.*)$)",
"^[./]"
],
"importOrderSeparation": true,
"importOrderSortSpecifiers": true,
"importOrderGroupNamespaceSpecifiers": true
"trailingComma": "none"
}

1
.vscode/launch.json vendored
View File

@@ -8,7 +8,6 @@
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/next",
"runtimeArgs": ["dev"],
"cwd": "${workspaceFolder}",
"port": 9229,
"smartStep": true,
"console": "integratedTerminal",
"skipFiles": ["<node_internals>/**"],

View File

@@ -1,9 +1,7 @@
import * as React from 'react'
import { PageHead } from './PageHead'
import styles from './styles.module.css'
export const ErrorPage: React.FC<{ statusCode: number }> = ({ statusCode }) => {
export function ErrorPage({ statusCode }: { statusCode: number }) {
const title = 'Error'
return (

View File

@@ -1,5 +1,3 @@
import * as React from 'react'
import { FaEnvelopeOpenText } from '@react-icons/all-files/fa/FaEnvelopeOpenText'
import { FaGithub } from '@react-icons/all-files/fa/FaGithub'
import { FaLinkedin } from '@react-icons/all-files/fa/FaLinkedin'
@@ -9,6 +7,7 @@ import { FaYoutube } from '@react-icons/all-files/fa/FaYoutube'
import { FaZhihu } from '@react-icons/all-files/fa/FaZhihu'
import { IoMoonSharp } from '@react-icons/all-files/io5/IoMoonSharp'
import { IoSunnyOutline } from '@react-icons/all-files/io5/IoSunnyOutline'
import * as React from 'react'
import * as config from '@/lib/config'
import { useDarkMode } from '@/lib/use-dark-mode'
@@ -17,7 +16,7 @@ import styles from './styles.module.css'
// TODO: merge the data and icons from PageSocial with the social links in Footer
export const FooterImpl: React.FC = () => {
export function FooterImpl() {
const [hasMounted, setHasMounted] = React.useState(false)
const { isDarkMode, toggleDarkMode } = useDarkMode()
const currentYear = new Date().getFullYear()
@@ -36,7 +35,9 @@ export const FooterImpl: React.FC = () => {
return (
<footer className={styles.footer}>
<div className={styles.copyright}>Copyright {currentYear} {config.author}</div>
<div className={styles.copyright}>
Copyright {currentYear} {config.author}
</div>
<div className={styles.settings}>
{hasMounted && (

View File

@@ -1,8 +1,6 @@
import * as React from 'react'
import styles from './styles.module.css'
export const GitHubShareButton: React.FC = () => {
export function GitHubShareButton() {
return (
<a
href='https://github.com/transitive-bullshit/nextjs-notion-starter-kit'

View File

@@ -1,10 +1,10 @@
import * as React from 'react'
import { LoadingIcon } from './LoadingIcon'
import styles from './styles.module.css'
export const Loading: React.FC = () => (
<div className={styles.container}>
<LoadingIcon />
</div>
)
export function Loading() {
return (
<div className={styles.container}>
<LoadingIcon />
</div>
)
}

View File

@@ -1,10 +1,8 @@
import * as React from 'react'
import cs from 'classnames'
import styles from './styles.module.css'
export const LoadingIcon = (props) => {
export function LoadingIcon(props: any) {
const { className, ...rest } = props
return (
<svg

View File

@@ -1,19 +1,18 @@
import * as React from 'react'
import cs from 'classnames'
import dynamic from 'next/dynamic'
import Image from 'next/image'
import Image from 'next/legacy/image'
import Link from 'next/link'
import { useRouter } from 'next/router'
import cs from 'classnames'
import { PageBlock } from 'notion-types'
import { type PageBlock } from 'notion-types'
import { formatDate, getBlockTitle, getPageProperty } from 'notion-utils'
import * as React from 'react'
import BodyClassName from 'react-body-classname'
import { NotionRenderer } from 'react-notion-x'
import TweetEmbed from 'react-tweet-embed'
import { useSearchParam } from 'react-use'
import type * as types from '@/lib/types'
import * as config from '@/lib/config'
import * as types from '@/lib/types'
import { mapImageUrl } from '@/lib/map-image-url'
import { getCanonicalPageUrl, mapPageUrl } from '@/lib/map-page-url'
import { searchNotion } from '@/lib/search-notion'
@@ -97,7 +96,7 @@ const Modal = dynamic(
}
)
const Tweet = ({ id }: { id: string }) => {
function Tweet({ id }: { id: string }) {
return <TweetEmbed tweetId={id} />
}
@@ -142,12 +141,12 @@ const propertyTextValue = (
return defaultFn()
}
export const NotionPage: React.FC<types.PageProps> = ({
export function NotionPage({
site,
recordMap,
error,
pageId
}) => {
}: types.PageProps) {
const router = useRouter()
const lite = useSearchParam('lite')

View File

@@ -1,9 +1,8 @@
import * as React from 'react'
import * as types from 'notion-types'
import type * as types from 'notion-types'
import { IoMoonSharp } from '@react-icons/all-files/io5/IoMoonSharp'
import { IoSunnyOutline } from '@react-icons/all-files/io5/IoSunnyOutline'
import cs from 'classnames'
import * as React from 'react'
import { Breadcrumbs, Header, Search, useNotionContext } from 'react-notion-x'
import { isSearchEnabled, navigationLinks, navigationStyle } from '@/lib/config'
@@ -11,7 +10,7 @@ import { useDarkMode } from '@/lib/use-dark-mode'
import styles from './styles.module.css'
const ToggleThemeButton = () => {
function ToggleThemeButton() {
const [hasMounted, setHasMounted] = React.useState(false)
const { isDarkMode, toggleDarkMode } = useDarkMode()
@@ -33,9 +32,11 @@ const ToggleThemeButton = () => {
)
}
export const NotionPageHeader: React.FC<{
export function NotionPageHeader({
block
}: {
block: types.CollectionViewPageBlock | types.PageBlock
}> = ({ block }) => {
}) {
const { components, mapPageUrl } = useNotionContext()
if (navigationStyle === 'default') {

View File

@@ -1,11 +1,9 @@
import * as React from 'react'
import * as types from '@/lib/types'
import type * as types from '@/lib/types'
import { PageHead } from './PageHead'
import styles from './styles.module.css'
export const Page404: React.FC<types.PageProps> = ({ site, pageId, error }) => {
export function Page404({ site, pageId, error }: types.PageProps) {
const title = site?.name || 'Notion Page Not Found'
return (

View File

@@ -1,5 +1,3 @@
import * as React from 'react'
import { AiOutlineRetweet } from '@react-icons/all-files/ai/AiOutlineRetweet'
import { IoHeartOutline } from '@react-icons/all-files/io5/IoHeartOutline'
@@ -8,7 +6,7 @@ import styles from './styles.module.css'
/**
* @see https://developer.twitter.com/en/docs/twitter-for-websites/web-intents/overview
*/
export const PageActions: React.FC<{ tweet: string }> = ({ tweet }) => {
export function PageActions({ tweet }: { tweet: string }) {
return (
<div className={styles.pageActions}>
<a

View File

@@ -1,17 +1,19 @@
import * as React from 'react'
import { Block, ExtendedRecordMap } from 'notion-types'
import { type Block, type ExtendedRecordMap } from 'notion-types'
import { getPageTweet } from '@/lib/get-page-tweet'
import { PageActions } from './PageActions'
import { PageSocial } from './PageSocial'
export const PageAside: React.FC<{
export function PageAside({
block,
recordMap,
isBlogPost
}: {
block: Block
recordMap: ExtendedRecordMap
isBlogPost: boolean
}> = ({ block, recordMap, isBlogPost }) => {
}) {
if (!block) {
return null
}

View File

@@ -1,18 +1,22 @@
import * as React from 'react'
import Head from 'next/head'
import type * as types from '@/lib/types'
import * as config from '@/lib/config'
import * as types from '@/lib/types'
import { getSocialImageUrl } from '@/lib/get-social-image-url'
export const PageHead: React.FC<
types.PageProps & {
title?: string
description?: string
image?: string
url?: string
}
> = ({ site, title, description, pageId, image, url }) => {
export function PageHead({
site,
title,
description,
pageId,
image,
url
}: types.PageProps & {
title?: string
description?: string
image?: string
url?: string
}) {
const rssFeedUrl = `${config.host}/feed`
title = title ?? site?.name
@@ -30,13 +34,20 @@ export const PageHead: React.FC<
/>
<meta name='apple-mobile-web-app-capable' content='yes' />
<meta
name='apple-mobile-web-app-status-bar-style'
content='black'
/>
<meta name='apple-mobile-web-app-status-bar-style' content='black' />
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#fefffe" key="theme-color-light"/>
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#2d3439" key="theme-color-dark"/>
<meta
name='theme-color'
media='(prefers-color-scheme: light)'
content='#fefffe'
key='theme-color-light'
/>
<meta
name='theme-color'
media='(prefers-color-scheme: dark)'
content='#2d3439'
key='theme-color-dark'
/>
<meta name='robots' content='index,follow' />
<meta property='og:type' content='website' />

View File

@@ -101,10 +101,10 @@
}
.youtube .actionBgPane {
background: #FF0000;
background: #ff0000;
}
.youtube:hover {
border-color: #FF0000;
border-color: #ff0000;
}
.medium .actionBgPane {

View File

@@ -1,5 +1,4 @@
import * as React from 'react'
import type * as React from 'react'
import cs from 'classnames'
import * as config from '@/lib/config'
@@ -70,7 +69,7 @@ const socialLinks: SocialLink[] = [
}
].filter(Boolean)
export const PageSocial: React.FC = () => {
export function PageSocial() {
return (
<div className={styles.pageSocial}>
{socialLinks.map((action) => (

View File

@@ -4,20 +4,20 @@ Suggestions and pull requests are highly encouraged. Have a look at the [open is
## Development
To develop the project locally, you'll need a recent version of Node.js and `yarn` v1 installed globally.
To develop the project locally, you'll need a recent version of Node.js and `pnpm` installed globally.
To get started, clone the repo and run `yarn` from the root directory:
To get started, clone the repo and run `pnpm` from the root directory:
```bash
git clone https://github.com/transitive-bullshit/nextjs-notion-starter-kit
cd nextjs-notion-starter-kit
yarn
pnpm
```
Now that your dependencies are installed, you can run the local Next.js dev server:
```bash
yarn dev
pnpm dev
```
You should now be able to open `http://localhost:3000` to view the webapp.
@@ -27,7 +27,7 @@ You should now be able to open `http://localhost:3000` to view the webapp.
To build for production, you can run:
```bash
yarn build
pnpm build
```
Which just runs `next build` under the hood.
@@ -36,48 +36,18 @@ Which just runs `next build` under the hood.
If you are making changes to `react-notion-x` and want to test them out with `nextjs-notion-starter-kit`, you'll first need to [set up and build `react-notion-x` locally](https://github.com/NotionX/react-notion-x/blob/master/contributing.md).
Once you have `react-notion-x` set up locally, run `yarn link` from each `react-notion-x` package:
Once you have `react-notion-x` set up and built locally, you can link these local deps into `nextjs-notion-starter-kit`:
```bash
# from react-notion-x clone
cd packages/react-notion-x
yarn link
cd ../packages/notion-utils
yarn link
cd ../packages/notion-types
yarn link
cd ../packages/notion-client
yarn link
pnpm deps:link
```
Now you can link these local deps into `nextjs-notion-starter-kit`:
```bash
# from nextjs-notion-starter-kit
yarn deps:link
```
The last step is to make sure that the Next.js project and these local dependencies are all pointing to the same versions of `react` and `react-dom`.
```bash
# from react-notion-x clone
cd node_modules/react
yarn link
cd ../react-dom
yarn link
```
```bash
# from nextjs-notion-starter-kit
yarn link react react-dom
```
With this setup, in one tab, you can run `yarn dev` to keep `react-notion-x` up-to-date, and in another tab, you can run `yarn dev` to keep `nextjs-notion-starter-kit` up-to-date.
With this setup, in one tab, you can run `pnpm dev` to keep `react-notion-x` up-to-date, and in another tab, you can run `pnpm dev` to keep `nextjs-notion-starter-kit` up-to-date.
### Gotchas
Whenever you make a change to one of the `react-notion-x` packages, it will automatically be recompiled into its respective `build` folder, and the `yarn dev` from `nextjs-notion-starter-kit` should hot-reload it in the browser.
Whenever you make a change to one of the `react-notion-x` packages, it will automatically be recompiled into its respective `build` folder, and the `pnpm dev` from `nextjs-notion-starter-kit` should hot-reload it in the browser.
Sometimes, this process gets a little out of whack, and if you're not sure what's going on, I usually just quit one or both of the `yarn dev` commands and restart them.
Sometimes, this process gets a little out of whack, and if you're not sure what's going on, I usually just quit one or both of the `pnpm dev` commands and restart them.
If you're seeing something unexpected while debugging with Next.js, try running `rm -rf .next` to refresh the Next.js cache before running `yarn dev` again.
If you're seeing something unexpected while debugging with Next.js, try running `rm -rf .next` to refresh the Next.js cache before running `pnpm dev` again.

View File

@@ -1,4 +1,4 @@
import { PageProps } from './types'
import { type PageProps } from './types'
export async function pageAcl({
site,

View File

@@ -7,7 +7,7 @@ export function bootstrap() {
██║ ██╔══██╗██╔══██║██║╚██╗██║╚════██║██║ ██║ ██║╚██╗ ██╔╝██╔══╝ ██╔══██╗╚════██║
██║ ██║ ██║██║ ██║██║ ╚████║███████║██║ ██║ ██║ ╚████╔╝ ███████╗ ██████╔╝███████║
╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═══╝ ╚══════╝ ╚═════╝ ╚══════╝
This site is built using Notion, Next.js, and https://github.com/NotionX/react-notion-x.
`)
}

View File

@@ -5,15 +5,15 @@
* for optional depenencies.
*/
import { parsePageId } from 'notion-utils'
import { PostHogConfig } from 'posthog-js'
import { type PostHogConfig } from 'posthog-js'
import { getEnv, getSiteConfig } from './get-config-value'
import { NavigationLink } from './site-config'
import { type NavigationLink } from './site-config'
import {
NavigationStyle,
PageUrlOverridesInverseMap,
PageUrlOverridesMap,
Site
type NavigationStyle,
type PageUrlOverridesInverseMap,
type PageUrlOverridesMap,
type Site
} from './types'
export const rootNotionPageId: string = parsePageId(

View File

@@ -1,4 +1,4 @@
import { ExtendedRecordMap } from 'notion-types'
import { type ExtendedRecordMap } from 'notion-types'
import {
getCanonicalPageId as getCanonicalPageIdImpl,
parsePageId

View File

@@ -1,5 +1,5 @@
import rawSiteConfig from '../site.config'
import { SiteConfig } from './site-config'
import { type SiteConfig } from './site-config'
if (!rawSiteConfig) {
throw new Error(`Config error: invalid site.config.ts`)

View File

@@ -1,6 +1,6 @@
import { getPageProperty } from 'notion-utils'
import * as types from './types'
import type * as types from './types'
export function getPageTweet(
block: types.Block,

View File

@@ -1,8 +1,8 @@
import { getAllPagesInSpace, uuidToId, getPageProperty } from 'notion-utils'
import { getAllPagesInSpace, getPageProperty, uuidToId } from 'notion-utils'
import pMemoize from 'p-memoize'
import type * as types from './types'
import * as config from './config'
import * as types from './types'
import { includeNotionIdInUrls } from './config'
import { getCanonicalPageId } from './get-canonical-page-id'
import { notion } from './notion-api'
@@ -25,15 +25,15 @@ const getAllPages = pMemoize(getAllPagesImpl, {
cacheKey: (...args) => JSON.stringify(args)
})
const getPage = async (pageId: string, ...args) => {
console.log('\nnotion getPage', uuidToId(pageId))
return notion.getPage(pageId, ...args)
}
async function getAllPagesImpl(
rootNotionPageId: string,
rootNotionSpaceId: string
): Promise<Partial<types.SiteMap>> {
const getPage = async (pageId: string, ...args) => {
console.log('\nnotion getPage', uuidToId(pageId))
return notion.getPage(pageId, ...args)
}
const pageMap = await getAllPagesInSpace(
rootNotionPageId,
rootNotionSpaceId,
@@ -48,7 +48,9 @@ async function getAllPagesImpl(
}
const block = recordMap.block[pageId]?.value
if (!(getPageProperty<boolean|null>('Public', block, recordMap) ?? true)) {
if (
!(getPageProperty<boolean | null>('Public', block, recordMap) ?? true)
) {
return map
}

View File

@@ -1,9 +1,9 @@
import { Block } from 'notion-types'
import { type Block } from 'notion-types'
import { defaultMapImageUrl } from 'react-notion-x'
import { defaultPageCover, defaultPageIcon } from './config'
export const mapImageUrl = (url: string, block: Block) => {
export const mapImageUrl = (url: string | undefined, block: Block) => {
if (url === defaultPageCover || url === defaultPageIcon) {
return url
}

View File

@@ -1,9 +1,9 @@
import { ExtendedRecordMap } from 'notion-types'
import { type ExtendedRecordMap } from 'notion-types'
import { parsePageId, uuidToId } from 'notion-utils'
import { includeNotionIdInUrls } from './config'
import { getCanonicalPageId } from './get-canonical-page-id'
import { Site } from './types'
import { type Site } from './types'
// include UUIDs in page URLs during local development but not in production
// (they're nice for debugging and speed up local dev)

View File

@@ -1,4 +1,8 @@
import { ExtendedRecordMap, SearchParams, SearchResults } from 'notion-types'
import {
type ExtendedRecordMap,
type SearchParams,
type SearchResults
} from 'notion-types'
import { mergeRecordMaps } from 'notion-utils'
import pMap from 'p-map'
import pMemoize from 'p-memoize'

View File

@@ -1,6 +1,10 @@
import got from 'got'
import ky from 'ky'
import lqip from 'lqip-modern'
import { ExtendedRecordMap, PreviewImage, PreviewImageMap } from 'notion-types'
import {
type ExtendedRecordMap,
type PreviewImage,
type PreviewImageMap
} from 'notion-types'
import { getPageImageUrls, normalizeUrl } from 'notion-utils'
import pMap from 'p-map'
import pMemoize from 'p-memoize'
@@ -49,7 +53,7 @@ async function createPreviewImage(
console.warn(`redis error get "${cacheKey}"`, err.message)
}
const { body } = await got(url, { responseType: 'buffer' })
const body = await ky(url).arrayBuffer()
const result = await lqip(body)
console.log('lqip', { ...result.metadata, url, cacheKey })

View File

@@ -1,4 +1,4 @@
import { ExtendedRecordMap } from 'notion-types'
import { type ExtendedRecordMap } from 'notion-types'
import { parsePageId } from 'notion-utils'
import * as acl from './acl'

View File

@@ -1,14 +1,12 @@
// import ky from 'ky'
import ExpiryMap from 'expiry-map'
import fetch from 'isomorphic-unfetch'
import pMemoize from 'p-memoize'
import * as types from './types'
import type * as types from './types'
import { api } from './config'
export const searchNotion = pMemoize(searchNotionImpl, {
cacheKey: (args) => args[0]?.query,
cache: new ExpiryMap(10000)
cache: new ExpiryMap(10_000)
})
async function searchNotionImpl(
@@ -29,7 +27,7 @@ async function searchNotionImpl(
// convert non-2xx HTTP responses into errors
const error: any = new Error(res.statusText)
error.response = res
return Promise.reject(error)
throw error
})
.then((res) => res.json())

View File

@@ -1,4 +1,4 @@
import * as types from './types'
import type * as types from './types'
export interface SiteConfig {
rootNotionPageId: string
@@ -16,7 +16,7 @@ export interface SiteConfig {
newsletter?: string
youtube?: string
zhihu?: string
mastodon?: string;
mastodon?: string
defaultPageIcon?: string | null
defaultPageCover?: string | null

View File

@@ -1,5 +1,6 @@
import { ExtendedRecordMap, PageMap } from 'notion-types'
import { ParsedUrlQuery } from 'querystring'
import { type ParsedUrlQuery } from 'node:querystring'
import { type ExtendedRecordMap, type PageMap } from 'notion-types'
export * from 'notion-types'

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2023 Travis Fischer
Copyright (c) 2024 Travis Fischer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

2
next-env.d.ts vendored
View File

@@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.

View File

@@ -1,22 +1,66 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const withBundleAnalyzer = require('@next/bundle-analyzer')({
import bundleAnalyzer from '@next/bundle-analyzer'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const withBundleAnalyzer = bundleAnalyzer({
enabled: process.env.ANALYZE === 'true'
})
module.exports = withBundleAnalyzer({
export default withBundleAnalyzer({
staticPageGenerationTimeout: 300,
images: {
domains: [
'www.notion.so',
'notion.so',
'images.unsplash.com',
'pbs.twimg.com',
'abs.twimg.com',
's3.us-west-2.amazonaws.com',
'transitivebullsh.it'
remotePatterns: [
{
protocol: 'https',
hostname: 'www.notion.so',
pathname: '**'
},
{
protocol: 'https',
hostname: 'notion.so',
pathname: '**'
},
{
protocol: 'https',
hostname: 'images.unsplash.com',
pathname: '**'
},
{
protocol: 'https',
hostname: 'pbs.twimg.com',
pathname: '**'
},
{
protocol: 'https',
hostname: 'abs.twimg.com',
pathname: '**'
},
{
protocol: 'https',
hostname: 's3.us-west-2.amazonaws.com',
pathname: '**'
},
{
protocol: 'https',
hostname: 'transitivebullsh.it',
pathname: '**'
}
],
formats: ['image/avif', 'image/webp'],
dangerouslyAllowSVG: true,
contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;"
},
webpack: (config, _context) => {
// Workaround for ensuring that `react` and `react-dom` resolve correctly
// when using a locally-linked version of `react-notion-x`.
// @see https://github.com/vercel/next.js/issues/50391
const dirname = path.dirname(fileURLToPath(import.meta.url))
config.resolve.alias.react = path.resolve(dirname, 'node_modules/react')
config.resolve.alias['react-dom'] = path.resolve(
dirname,
'node_modules/react-dom'
)
return config
}
})

View File

@@ -7,21 +7,26 @@
"repository": "transitive-bullshit/nextjs-notion-starter-kit",
"license": "MIT",
"engines": {
"node": ">=16"
"node": ">=18"
},
"type": "module",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"deploy": "vercel deploy",
"deps": "run-s deps:*",
"deps:update": "[ -z $GITHUB_ACTIONS ] && yarn add notion-client notion-types notion-utils react-notion-x || echo 'Skipping deps:update on CI'",
"deps:link": "[ -z $GITHUB_ACTIONS ] && yarn link notion-client notion-types notion-utils react-notion-x || echo 'Skipping deps:link on CI'",
"deps:update": "[ -z $GITHUB_ACTIONS ] && pnpm up -L notion-client notion-types notion-utils react-notion-x || echo 'Skipping deps:update on CI'",
"deps:link": "[ -z $GITHUB_ACTIONS ] && run-s deps:link:* || echo 'Skipping deps:update on CI'",
"deps:unlink": "[ -z $GITHUB_ACTIONS ] && pnpm add notion-client notion-types notion-utils react-notion-x || echo 'Skipping deps:update on CI'",
"deps:link:notion-types": "pnpm link ../react-notion-x/packages/notion-types",
"deps:link:notion-utils": "pnpm link ../react-notion-x/packages/notion-utils",
"deps:link:notion-client": "pnpm link ../react-notion-x/packages/notion-client",
"deps:link:react-notion-x": "pnpm link ../react-notion-x/packages/react-notion-x",
"analyze": "cross-env ANALYZE=true next build",
"analyze:server": "cross-env BUNDLE_ANALYZE=server next build",
"analyze:browser": "cross-env BUNDLE_ANALYZE=browser next build",
"test": "run-p test:*",
"test:lint": "eslint '**/*.{ts,tsx}'",
"test:lint": "eslint .",
"test:prettier": "prettier '**/*.{js,jsx,ts,tsx}' --check"
},
"dependencies": {
@@ -29,44 +34,39 @@
"@keyvhq/core": "^1.6.9",
"@keyvhq/redis": "^1.6.10",
"@react-icons/all-files": "^4.1.0",
"@vercel/og": "^0.0.19",
"classnames": "^2.3.1",
"date-fns": "^2.28.0",
"@vercel/og": "^0.6.3",
"classnames": "^2.5.1",
"date-fns": "^2.30.0",
"expiry-map": "^2.0.0",
"fathom-client": "^3.4.1",
"got": "^12.0.3",
"isomorphic-unfetch": "^3.1.0",
"lqip-modern": "^2.0.0",
"next": "12",
"notion-client": "^6.15.6",
"notion-types": "^6.15.6",
"notion-utils": "^6.15.6",
"p-map": "^5.3.0",
"p-memoize": "^6.0.1",
"ky": "^1.7.2",
"lqip-modern": "^2.1.0",
"next": "^15.0.2",
"notion-client": "^7.0.1",
"notion-types": "^7.0.1",
"notion-utils": "^7.0.1",
"p-map": "^7.0.2",
"p-memoize": "^7.1.1",
"posthog-js": "^1.20.2",
"prismjs": "^1.29.0",
"react": "^18.2.0",
"react-body-classname": "^1.3.1",
"react-dom": "^18.2.0",
"react-notion-x": "^6.15.6",
"react-notion-x": "^7.0.1",
"react-tweet-embed": "^2.0.0",
"react-use": "^17.4.2",
"rss": "^1.2.2"
},
"devDependencies": {
"@next/bundle-analyzer": "^12.3.1",
"@trivago/prettier-plugin-sort-imports": "^3.3.1",
"@types/node": "^18.8.5",
"@fisch0920/eslint-config": "^1.4.0",
"@next/bundle-analyzer": "^15.0.2",
"@types/node": "^22.8.6",
"@types/react": "^18.0.21",
"@typescript-eslint/eslint-plugin": "^5.40.0",
"@typescript-eslint/parser": "^5.40.0",
"cross-env": "^7.0.2",
"eslint": "^8.25.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-react": "^7.31.10",
"eslint-plugin-react-hooks": "^4.6.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.7.1",
"typescript": "^4.8.4"
"eslint": "^8.57.1",
"npm-run-all2": "^7.0.1",
"prettier": "^3.3.3",
"typescript": "^5.6.3"
},
"overrides": {
"cacheable-request": {

View File

@@ -1,3 +1 @@
import { Page404 } from '@/components/Page404'
export default Page404
export { Page404 as default } from '@/components/Page404'

View File

@@ -1,11 +1,10 @@
import * as React from 'react'
import { GetStaticProps } from 'next'
import { type GetStaticProps } from 'next'
import { NotionPage } from '@/components/NotionPage'
import { domain, isDev } from '@/lib/config'
import { getSiteMap } from '@/lib/get-site-map'
import { resolveNotionPage } from '@/lib/resolve-notion-page'
import { PageProps, Params } from '@/lib/types'
import { type PageProps, type Params } from '@/lib/types'
export const getStaticProps: GetStaticProps<PageProps, Params> = async (
context

View File

@@ -1,16 +1,10 @@
// global styles shared across the entire site
import * as React from 'react'
import type { AppProps } from 'next/app'
import { useRouter } from 'next/router'
import * as Fathom from 'fathom-client'
// used for rendering equations (optional)
import 'katex/dist/katex.min.css'
import posthog from 'posthog-js'
// used for code syntax highlighting (optional)
import 'prismjs/themes/prism-coy.css'
// core styles shared by all of react-notion-x (required)
import 'react-notion-x/src/styles.css'
// global styles shared across the entire site
import 'styles/global.css'
// this might be better for dark mode
// import 'prismjs/themes/prism-okaidia.css'
@@ -19,6 +13,12 @@ import 'styles/notion.css'
// global style overrides for prism theme (optional)
import 'styles/prism-theme.css'
import type { AppProps } from 'next/app'
import * as Fathom from 'fathom-client'
import { useRouter } from 'next/router'
import posthog from 'posthog-js'
import * as React from 'react'
import { bootstrap } from '@/lib/bootstrap-client'
import {
fathomConfig,

View File

@@ -1,7 +1,5 @@
import * as React from 'react'
import Document, { Head, Html, Main, NextScript } from 'next/document'
import { IconContext } from '@react-icons/all-files'
import Document, { Head, Html, Main, NextScript } from 'next/document'
export default class MyDocument extends Document {
render() {

View File

@@ -1,3 +1 @@
import { ErrorPage } from '@/components/ErrorPage'
export default ErrorPage
export { ErrorPage as default } from '@/components/ErrorPage'

View File

@@ -1,7 +1,6 @@
import { NextApiRequest, NextApiResponse } from 'next'
import got from 'got'
import { PageBlock } from 'notion-types'
import ky from 'ky'
import { type NextApiRequest, type NextApiResponse } from 'next'
import { type PageBlock } from 'notion-types'
import {
getBlockIcon,
getBlockTitle,
@@ -13,9 +12,12 @@ import {
import * as libConfig from '@/lib/config'
import { mapImageUrl } from '@/lib/map-image-url'
import { notion } from '@/lib/notion-api'
import { NotionPageInfo } from '@/lib/types'
import { type NotionPageInfo } from '@/lib/types'
export default async (req: NextApiRequest, res: NextApiResponse) => {
export default async function notionPageInfo(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'POST') {
return res.status(405).send({ error: 'method not allowed' })
}
@@ -125,9 +127,9 @@ async function isUrlReachable(url: string | null): Promise<boolean> {
}
try {
await got.head(url)
await ky.head(url)
return true
} catch (err) {
} catch {
return false
}
}

View File

@@ -1,9 +1,12 @@
import { NextApiRequest, NextApiResponse } from 'next'
import { type NextApiRequest, type NextApiResponse } from 'next'
import * as types from '../../lib/types'
import type * as types from '../../lib/types'
import { search } from '../../lib/notion'
export default async (req: NextApiRequest, res: NextApiResponse) => {
export default async function searchNotion(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'POST') {
return res.status(405).send({ error: 'method not allowed' })
}

View File

@@ -1,21 +1,20 @@
import * as React from 'react'
import { NextRequest } from 'next/server'
import { ImageResponse } from '@vercel/og'
import ky from 'ky'
import { type NextRequest } from 'next/server'
import { api, apiHost, rootNotionPageId } from '@/lib/config'
import { NotionPageInfo } from '@/lib/types'
import { type NotionPageInfo } from '@/lib/types'
const interRegularFontP = fetch(
const interRegularFontP = ky(
new URL('../../public/fonts/Inter-Regular.ttf', import.meta.url)
).then((res) => res.arrayBuffer())
).arrayBuffer()
const interBoldFontP = fetch(
const interBoldFontP = ky(
new URL('../../public/fonts/Inter-SemiBold.ttf', import.meta.url)
).then((res) => res.arrayBuffer())
).arrayBuffer()
export const config = {
runtime: 'experimental-edge'
runtime: 'edge'
}
export default async function OGImage(req: NextRequest) {

View File

@@ -1,6 +1,5 @@
import type { GetServerSideProps } from 'next'
import { ExtendedRecordMap } from 'notion-types'
import { type ExtendedRecordMap } from 'notion-types'
import {
getBlockParentPage,
getBlockTitle,
@@ -67,8 +66,8 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
const date = lastUpdatedTime
? new Date(lastUpdatedTime)
: publishedTime
? new Date(publishedTime)
: undefined
? new Date(publishedTime)
: undefined
const socialImageUrl = getSocialImageUrl(pageId)
feed.item({
@@ -98,4 +97,6 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
return { props: {} }
}
export default () => null
export default function noop() {
return null
}

View File

@@ -1,5 +1,3 @@
import * as React from 'react'
import { NotionPage } from '@/components/NotionPage'
import { domain } from '@/lib/config'
import { resolveNotionPage } from '@/lib/resolve-notion-page'

View File

@@ -42,4 +42,6 @@ Sitemap: ${host}/sitemap.xml
}
}
export default () => null
export default function noop() {
return null
}

View File

@@ -1,8 +1,8 @@
import type { GetServerSideProps } from 'next'
import type { SiteMap } from '@/lib/types'
import { host } from '@/lib/config'
import { getSiteMap } from '@/lib/get-site-map'
import type { SiteMap } from '@/lib/types'
export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
if (req.method !== 'GET') {
@@ -54,4 +54,6 @@ const createSitemap = (siteMap: SiteMap) =>
</urlset>
`
export default () => null
export default function noop() {
return null
}

5144
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +1,16 @@
{
"compilerOptions": {
"target": "es2016",
"target": "es2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"experimentalDecorators": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"moduleResolution": "bundler",
"moduleDetection": "force",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",

3892
yarn.lock

File diff suppressed because it is too large Load Diff