├── .env.example
├── public
├── favicon.ico
├── icons
│ ├── favicon.ico
│ ├── apple-touch-icon.png
│ ├── maskable_icon_x48.png
│ ├── maskable_icon_x72.png
│ ├── maskable_icon_x96.png
│ ├── maskable_icon_x128.png
│ ├── maskable_icon_x192.png
│ ├── maskable_icon_x384.png
│ └── maskable_icon_x512.png
├── robots.txt
├── sitemap.xml.js
├── fallback-development.js
└── manifest.json
├── .prettierrc.json
├── pages
├── 404.tsx
├── 500.tsx
├── _offline.tsx
├── _app.tsx
├── _document.tsx
├── index.tsx
└── [slug].tsx
├── next-env.d.ts
├── postcss.config.js
├── .eslintrc.json
├── next.config.js
├── tsconfig.json
├── style
└── global.css
├── .gitignore
├── webpack.config.js
├── lib
└── client.ts
├── components
├── ScrollToTop.tsx
├── Footer.tsx
├── Template.tsx
└── Layout.tsx
├── tailwind.config.js
├── package.json
└── README.md
/.env.example:
--------------------------------------------------------------------------------
1 | NOTION_TOKEN=
2 | NOTION_DATABASE_ID=
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minime89-maker/next.js-notion-blog/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/icons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minime89-maker/next.js-notion-blog/HEAD/public/icons/favicon.ico
--------------------------------------------------------------------------------
/public/icons/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minime89-maker/next.js-notion-blog/HEAD/public/icons/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/icons/maskable_icon_x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minime89-maker/next.js-notion-blog/HEAD/public/icons/maskable_icon_x48.png
--------------------------------------------------------------------------------
/public/icons/maskable_icon_x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minime89-maker/next.js-notion-blog/HEAD/public/icons/maskable_icon_x72.png
--------------------------------------------------------------------------------
/public/icons/maskable_icon_x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minime89-maker/next.js-notion-blog/HEAD/public/icons/maskable_icon_x96.png
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "singleQuote": true,
4 | "trailingComma": "all",
5 | "singleAttributePerLine": true
6 | }
7 |
--------------------------------------------------------------------------------
/public/icons/maskable_icon_x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minime89-maker/next.js-notion-blog/HEAD/public/icons/maskable_icon_x128.png
--------------------------------------------------------------------------------
/public/icons/maskable_icon_x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minime89-maker/next.js-notion-blog/HEAD/public/icons/maskable_icon_x192.png
--------------------------------------------------------------------------------
/public/icons/maskable_icon_x384.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minime89-maker/next.js-notion-blog/HEAD/public/icons/maskable_icon_x384.png
--------------------------------------------------------------------------------
/public/icons/maskable_icon_x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minime89-maker/next.js-notion-blog/HEAD/public/icons/maskable_icon_x512.png
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # Block all crawlers for /accounts
2 | User-agent: *
3 | Disallow: /accounts
4 |
5 | # Allow all crawlers
6 | User-agent: *
7 | Allow: /
--------------------------------------------------------------------------------
/pages/404.tsx:
--------------------------------------------------------------------------------
1 | const Custom404 = () => {
2 | return (
3 | <>
4 |
404 - Page Not Found
5 | >
6 | )
7 | }
8 |
9 | export default Custom404
10 |
--------------------------------------------------------------------------------
/pages/500.tsx:
--------------------------------------------------------------------------------
1 | const Custom500 = () => {
2 | return (
3 | <>
4 | 500 - Internal Error
5 | >
6 | )
7 | }
8 |
9 | export default Custom500
10 |
--------------------------------------------------------------------------------
/pages/_offline.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Offline = () => {
4 | return (
5 |
6 |
No Internet connection
7 |
8 | )
9 | }
10 |
11 | export default Offline
12 |
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | // If you want to use other PostCSS plugins, see the following:
2 | // https://tailwindcss.com/docs/using-with-preprocessors
3 | module.exports = {
4 | plugins: {
5 | tailwindcss: {},
6 | autoprefixer: {},
7 | },
8 | }
9 |
--------------------------------------------------------------------------------
/public/sitemap.xml.js:
--------------------------------------------------------------------------------
1 | // manual sitemap for simple and static site
2 | ;
6 |
7 |
8 | https://notionblogs.vercel.app/
9 | 2021-06-10
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["unused-imports", "@typescript-eslint"],
3 | "extends": [
4 | "next/core-web-vitals",
5 | "plugin:@typescript-eslint/recommended",
6 | "prettier"
7 | ],
8 | "rules": {
9 | "@typescript-eslint/no-unused-vars": "off",
10 | "unused-imports/no-unused-imports": "error",
11 | "@typescript-eslint/no-explicit-any": "off",
12 | "@typescript-eslint/ban-ts-comment": "off"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const withPWA = require('next-pwa')
2 |
3 | module.exports = withPWA({
4 | images: {
5 | domains: [
6 | 's3-us-west-2.amazonaws.com',
7 | 'images.unsplash.com',
8 | 'miro.medium.com',
9 | 's3.us-west-2.amazonaws.com',
10 | 'images.pexels.com',
11 | 'i.imgur.com',
12 | 'thumbs.gfycat.com',
13 | ],
14 | },
15 | pwa: {
16 | dest: 'public',
17 | register: true,
18 | skipWaiting: true,
19 | disable: process.env.NODE_ENV === 'test',
20 | },
21 | })
22 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true
17 | },
18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"],
19 | "exclude": ["node_modules"]
20 | }
21 |
--------------------------------------------------------------------------------
/style/global.css:
--------------------------------------------------------------------------------
1 | .post-link {
2 | @apply transition-shadow;
3 | box-shadow: inset 0 -0.05em 0 #2c7a7b;
4 | }
5 | .post-link:hover {
6 | box-shadow: inset 0 -0.5em 0 #2c7a7b;
7 | }
8 | .frame-container {
9 | position: relative;
10 | padding-bottom: 56.25%;
11 | height: 0;
12 | margin: 14px 0;
13 | }
14 | iframe {
15 | position: absolute;
16 | top: 0;
17 | left: 0;
18 | width: 100%;
19 | height: 100%;
20 | }
21 |
22 | #nprogress .bar {
23 | background: #2c7a7b !important;
24 | }
25 |
26 | #nprogress .spinner {
27 | display: none;
28 | }
29 |
30 | #nprogress .spinner-icon {
31 | display: none;
32 | }
33 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
36 | # PWA files
37 | **/public/sw.js
38 | **/public/workbox-*.js
39 | **/public/worker-*.js
40 | **/public/sw.js.map
41 | **/public/workbox-*.js.map
42 | **/public/worker-*.js.map
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: './src/index.ts',
3 | output: {
4 | filename: './dist/bundle.js',
5 | },
6 | // Enable sourcemaps for debugging webpack's output.
7 | devtool: 'source-map',
8 | resolve: {
9 | // Add '.ts' and '.tsx' as resolvable extensions.
10 | extensions: ['', '.webpack.js', '.web.js', '.ts', '.tsx', '.js'],
11 | },
12 | module: {
13 | rules: [
14 | // All files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'.
15 | { test: /\.tsx?$/, loader: 'ts-loader' },
16 | // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
17 | { test: /\.js$/, loader: 'source-map-loader' },
18 | ],
19 | },
20 | }
21 |
--------------------------------------------------------------------------------
/public/fallback-development.js:
--------------------------------------------------------------------------------
1 | /******/ (() => { // webpackBootstrap
2 | /******/ "use strict";
3 | var __webpack_exports__ = {};
4 |
5 |
6 | self.fallback = async request => {
7 | // https://developer.mozilla.org/en-US/docs/Web/API/RequestDestination
8 | switch (request.destination) {
9 | case 'document':
10 | if (true) return caches.match("/_offline", {
11 | ignoreSearch: true
12 | });
13 |
14 | case 'image':
15 | if (false) {}
16 |
17 | case 'audio':
18 | if (false) {}
19 |
20 | case 'video':
21 | if (false) {}
22 |
23 | case 'font':
24 | if (false) {}
25 |
26 | case '':
27 | if (false) {}
28 |
29 | default:
30 | return Response.error();
31 | }
32 |
33 | ;
34 | };
35 | /******/ })()
36 | ;
--------------------------------------------------------------------------------
/lib/client.ts:
--------------------------------------------------------------------------------
1 | import { Client } from '@notionhq/client'
2 |
3 | const client = new Client({ auth: process.env.NOTION_TOKEN })
4 |
5 | export const getDatabase = async (databaseId: string) => {
6 | const response = await client.databases.query({
7 | database_id: databaseId,
8 | sorts: [
9 | {
10 | property: 'Date',
11 | direction: 'descending',
12 | },
13 | ],
14 | })
15 | return response.results
16 | }
17 |
18 | export const getPages = async (pageId: string) => {
19 | const response = await client.pages.retrieve({
20 | page_id: pageId,
21 | })
22 | return response
23 | }
24 |
25 | export const getBlocks = async (blockId: string) => {
26 | const response = await client.blocks.children.list({
27 | block_id: blockId,
28 | })
29 | return response.results
30 | }
31 |
--------------------------------------------------------------------------------
/components/ScrollToTop.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useEffect, useState } from 'react'
2 | import { ArrowUpCircle } from 'react-feather'
3 |
4 | const ScrollToTop = () => {
5 | const [isScrolled, setIsScrolled] = useState(false)
6 |
7 | const handleScroll = useCallback(() => {
8 | window.scrollTo({
9 | top: 0,
10 | behavior: 'smooth',
11 | })
12 | }, [])
13 |
14 | useEffect(() => {
15 | window.addEventListener('scroll', () => {
16 | if (window.scrollY > 180) {
17 | setIsScrolled(true)
18 | } else if (window.scrollY < 180) {
19 | setIsScrolled(false)
20 | }
21 | })
22 | })
23 |
24 | return (
25 | <>
26 |
35 | >
36 | )
37 | }
38 |
39 | export default ScrollToTop
40 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Blog",
3 | "name": "Notion's Blog",
4 | "description": "A simple Blog created with Notion",
5 | "icons": [
6 | {
7 | "src": "./icons/maskable_icon_x48.png",
8 | "sizes": "48x48",
9 | "type": "image/png"
10 | },
11 | {
12 | "src": "./icons/maskable_icon_x72.png",
13 | "sizes": "72x72",
14 | "type": "image/png"
15 | },
16 | {
17 | "src": "./icons/maskable_icon_x96.png",
18 | "sizes": "96x96",
19 | "type": "image/png"
20 | },
21 | {
22 | "src": "./icons/maskable_icon_x128.png",
23 | "sizes": "128x128",
24 | "type": "image/png"
25 | },
26 | {
27 | "src": "./icons/maskable_icon_x192.png",
28 | "sizes": "192x192",
29 | "type": "image/png"
30 | },
31 | {
32 | "src": "./icons/maskable_icon_x384.png",
33 | "sizes": "384x384",
34 | "type": "image/png"
35 | },
36 | {
37 | "src": "./icons/maskable_icon_x512.png",
38 | "sizes": "512x512",
39 | "type": "image/png"
40 | }
41 | ],
42 | "start_url": ".",
43 | "id": ".",
44 | "display": "standalone",
45 | "background_color": "#ffffff",
46 | "theme_color": "#222222"
47 | }
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mode: 'jit',
3 | purge: [
4 | './pages/**/*.{js,ts,jsx,tsx}',
5 | './components/**/*.{js,ts,jsx,tsx}',
6 | './public/**/*.html',
7 | ],
8 | darkMode: 'class', // or 'media' or 'class'
9 | theme: {
10 | fontFamily: {
11 | sans: ['IBM Plex Sans', 'Helvetica Neue', 'sans-serif'],
12 | serif: ['IBM Plex Serif', 'Georgia', 'serif'],
13 | mono: ['IBM Plex Mono', 'Menlo', 'monospace'],
14 | },
15 | extend: {
16 | colors: {
17 | backgroundColor: '#F9FAFB',
18 | backgroundDark: '#202023',
19 | button: '#319795',
20 | buttonDark: '#81E6D9',
21 | social: '#2C7A7B',
22 | socialDark: '#81E6D9',
23 | callout: '#F4F4F5',
24 | calloutDark: '#3a3a40',
25 | },
26 | textColor: {
27 | textPrimary: '#1A202C',
28 | textPrimaryDark: '#EEEEEE',
29 | textSecondary: '#262F40',
30 | textSecondaryDark: '#E0E0E0',
31 | textTertiary: '#6A6462',
32 | textTertiaryDark: '#ADADAD',
33 | textButton: '#fff',
34 | textLinks: '#81E6D9',
35 | },
36 | },
37 | },
38 | variants: {
39 | extend: {},
40 | },
41 | plugins: [],
42 | }
43 |
--------------------------------------------------------------------------------
/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import 'tailwindcss/tailwind.css'
2 | import '../style/global.css'
3 | import { ThemeProvider } from 'next-themes'
4 | import 'prismjs/themes/prism-tomorrow.css'
5 | import Router from 'next/router'
6 | import { AppProps } from 'next/app'
7 | import NProgress from 'nprogress'
8 | import 'nprogress/nprogress.css'
9 | import { useEffect } from 'react'
10 | import ScrollToTop from '../components/ScrollToTop'
11 |
12 | Router.events.on('routeChangeStart', () => NProgress.start())
13 | Router.events.on('routeChangeComplete', () => NProgress.done())
14 | Router.events.on('routeChangeError', () => NProgress.done())
15 |
16 | const MyApp = ({ Component, pageProps }: AppProps) => {
17 | // service worker
18 | useEffect(() => {
19 | if ('serviceWorker' in navigator) {
20 | window.addEventListener('load', function () {
21 | navigator.serviceWorker.register('/sw.js').then(
22 | function (registration) {
23 | registration
24 | },
25 | function (err) {
26 | err.message
27 | },
28 | )
29 | })
30 | }
31 | }, [])
32 |
33 | return (
34 |
35 |
36 |
37 |
38 | )
39 | }
40 |
41 | export default MyApp
42 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev": "next dev",
5 | "build": "next build",
6 | "start": "next start",
7 | "lint": "next lint"
8 | },
9 | "dependencies": {
10 | "@carbon/type": "10.37.0",
11 | "@notionhq/client": "0.4.3",
12 | "@types/jest": "27.4.1",
13 | "@types/lodash": "4.14.182",
14 | "@types/node": "17.0.25",
15 | "@types/react": "18.0.6",
16 | "date-fns": "2.25.0",
17 | "eslint-config-prettier": "^8.5.0",
18 | "eslint-plugin-unused-imports": "^2.0.0",
19 | "next": "latest",
20 | "next-themes": "0.1.1",
21 | "nprogress": "0.2.0",
22 | "prismjs": "1.28.0",
23 | "react": "17.0.2",
24 | "react-dom": "17.0.2",
25 | "react-feather": "2.0.9",
26 | "sharp": "0.29.3",
27 | "source-map-loader": "3.0.1",
28 | "ts-loader": "9.2.8",
29 | "typescript": "4.6.3"
30 | },
31 | "devDependencies": {
32 | "@types/nprogress": "0.2.0",
33 | "@types/prismjs": "1.26.0",
34 | "@typescript-eslint/eslint-plugin": "5.20.0",
35 | "@typescript-eslint/parser": "5.20.0",
36 | "autoprefixer": "10.2.6",
37 | "eslint": "8.14.0",
38 | "eslint-config-next": "12.0.3",
39 | "next-pwa": "5.4.0",
40 | "postcss": "8.3.5",
41 | "prettier": "2.6.2",
42 | "tailwindcss": "2.2.4"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Notion Blog
2 |
3 | This is a blog built with [Next.js](https://nextjs.org) using [Notion's API](https://developers.notion.com/) and [TailwindCSS](https://tailwdindcss.com).
4 |
5 | ## How to use
6 |
7 | To obtain a token and the database id please visit the [Notion Getting Started Guide](https://developers.notion.com/docs/getting-started).
8 |
9 | Create a file called `.env.local` in the root of the project and add the following lines:
10 |
11 | ```bash
12 | NOTION_TOKEN=
13 | NOTION_DATABASE_ID=
14 | ```
15 |
16 | Install dependencies
17 |
18 | ```bash
19 | npm install
20 | # or
21 | yarn
22 | ```
23 |
24 | Start the server with
25 |
26 | ```bash
27 | npm run dev
28 | # or
29 | yarn dev
30 | ```
31 |
32 | Go to [http://localhost:3000](http://localhost:3000).
33 |
34 | ## Deploy on Vercel
35 |
36 | The easiest way to deploy your Next.js app is to use [Vercel](https://vercel.com/).
37 |
38 | [](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fminime89-maker%2Fnext.js-notion-blog&env=NOTION_TOKEN,NOTION_DATABASE_ID&envDescription=Notion%20Token%20and%20Notion%20Database%20ID%20is%20required&envLink=https%3A%2F%2Fwww.notion.so%2Fmy-integrations&demo-title=Blog&demo-description=A%20blog%20example%20using%20Next.js%20and%20Notion's%20api&demo-url=https%3A%2F%2Fnotionblogs.vercel.app%2F&demo-image=https%3A%2F%2Fuser-images.githubusercontent.com%2F77694499%2F139641140-b61b5d2a-cb9c-45ed-988f-f18eed400003.png)
39 |
40 | ## Credits
41 |
42 | Thanks to [leerob](https://github.com/leerob) and the [samuelkraft](https://github.com/samuelkraft) for insipration.
43 |
--------------------------------------------------------------------------------
/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import { Html, Head, Main, NextScript } from 'next/document'
2 |
3 | const MyDocument = () => {
4 | return (
5 |
6 |
7 |
11 |
15 |
19 |
23 |
27 |
31 |
35 |
39 |
43 |
47 |
51 |
55 |
59 |
63 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | )
74 | }
75 |
76 | export default MyDocument
77 |
--------------------------------------------------------------------------------
/components/Footer.tsx:
--------------------------------------------------------------------------------
1 | import { Facebook, Twitter, Linkedin, GitHub } from 'react-feather'
2 | import Link from 'next/link'
3 |
4 | export type FooterProps = {
5 | facebook: string
6 | twitter: string
7 | linkedin: string
8 | github: string
9 | authorName?: string
10 | }
11 |
12 | const Footer = ({
13 | facebook,
14 | twitter,
15 | linkedin,
16 | github,
17 | authorName,
18 | }: FooterProps) => (
19 |
82 | )
83 |
84 | export default Footer
85 |
--------------------------------------------------------------------------------
/components/Template.tsx:
--------------------------------------------------------------------------------
1 | import Prism from 'prismjs'
2 | import React, { useEffect } from 'react'
3 |
4 | export type HeaderProps = {
5 | children: JSX.Element | JSX.Element[]
6 | }
7 |
8 | // @ts-ignore
9 | export const Text = ({ text }) => {
10 | {
11 | !text && null
12 | }
13 | return (
14 | text &&
15 | // @ts-ignore
16 | text.map((value, id) => {
17 | const {
18 | annotations: { bold, italic, underline, strikethrough, code, color },
19 | text,
20 | } = value
21 | return (
22 |
36 | {text.link ? (
37 |
43 | {text.content}
44 |
45 | ) : (
46 | text.content
47 | )}
48 |
49 | )
50 | })
51 | )
52 | }
53 |
54 | // Header components
55 | export const Heading_1 = ({ children }: HeaderProps) => {
56 | return (
57 |
58 |
59 | {children}
60 |
61 | )
62 | }
63 |
64 | export const Heading_2 = ({ children }: HeaderProps) => {
65 | return (
66 |
67 |
68 | {children}
69 |
70 | )
71 | }
72 |
73 | export const Heading_3 = ({ children }: HeaderProps) => {
74 | return (
75 |
76 |
77 | {children}
78 |
79 | )
80 | }
81 |
82 | // Callout component
83 | export const Callout = ({ children }: HeaderProps) => {
84 | return (
85 |
86 | {children}
87 |
88 | )
89 | }
90 |
91 | // Divider component
92 | export const Divider = () => {
93 | return
94 | }
95 |
96 | // Code component
97 | export const Code = ({
98 | children,
99 | language,
100 | }: {
101 | children: JSX.Element | JSX.Element[]
102 | language: string
103 | }) => {
104 | useEffect(() => {
105 | Prism.highlightAll()
106 | }, [])
107 |
108 | return (
109 |
110 | {children}
111 |
112 | )
113 | }
114 |
--------------------------------------------------------------------------------
/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import Head from 'next/head'
2 | import Link from 'next/link'
3 | import { getDatabase } from '../lib/client'
4 | import { format } from 'date-fns'
5 | import Layout, { LayoutProps } from '../components/Layout'
6 | import { useTheme } from 'next-themes'
7 |
8 | export const databaseId = process.env.NOTION_DATABASE_ID as string
9 |
10 | export type HomeProps = {
11 | database: Array<{ [key: string]: any }>
12 | author: { [key: string]: any }
13 | } & LayoutProps
14 |
15 | const Home = ({ database, author }: HomeProps) => {
16 | const { theme } = useTheme()
17 |
18 | return (
19 |
30 |
31 | {database[0].properties.Author.created_by.name} Blog
32 |
33 |
34 |
107 |
108 | )
109 | }
110 |
111 | export default Home
112 |
113 | export const getStaticProps = async () => {
114 | const database = await getDatabase(databaseId)
115 | return {
116 | props: {
117 | author: database[0],
118 | database: database.slice(1),
119 | },
120 | revalidate: 10,
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/pages/[slug].tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-ts-comment */
2 |
3 | import Image from 'next/image'
4 | import Head from 'next/head'
5 | import { GetStaticPaths, GetStaticProps } from 'next'
6 | import {
7 | Text,
8 | Heading_2,
9 | Code,
10 | Heading_1,
11 | Heading_3,
12 | Callout,
13 | Divider,
14 | } from '../components/Template'
15 | import { getDatabase, getPages, getBlocks } from '../lib/client'
16 | import { databaseId } from './index'
17 | import { format } from 'date-fns'
18 | import Layout from '../components/Layout'
19 | import { ParsedUrlQuery } from 'querystring'
20 |
21 | // @ts-ignore WIP
22 | const BlockPage = (block) => {
23 | const { type, id } = block
24 | const value = block[type]
25 |
26 | switch (type) {
27 | case 'paragraph':
28 | return (
29 |
30 |
34 |
35 | )
36 | case 'heading_1':
37 | return {value.text[0].plain_text}
38 | case 'heading_2':
39 | return {value.text[0].plain_text}
40 | case 'heading_3':
41 | return {value.text[0].plain_text}
42 | case 'callout':
43 | return (
44 |
45 | {value.icon.emoji} {value.text[0]?.plain_text}
46 |
47 | )
48 | case 'code':
49 | return (
50 |
54 | {value.text[0].plain_text}
55 |
56 | )
57 | case 'image':
58 | const src =
59 | value.type === 'external' ? value.external.url : value.file.url
60 | const caption = value.caption ? value.caption[0]?.plain_text : ''
61 | return (
62 |
63 |
72 |
73 | )
74 | case 'bulleted_list_item':
75 | case 'numbered_list_item':
76 | return (
77 |
78 |
79 |
80 | )
81 | case 'quote':
82 | return (
83 |
84 |
85 |
86 | )
87 | case 'video':
88 | return (
89 |
90 |
97 |
98 | )
99 | case 'embed':
100 | return (
101 |
102 |
107 |
108 | )
109 | case 'divider':
110 | return
111 | default:
112 | return `Unsupported block (${
113 | type === 'unsupported' ? 'unsupported' : type
114 | })`
115 | }
116 | }
117 |
118 | // @ts-ignore WIP
119 | const Pages = ({ pages, blocks, author }) => {
120 | return (
121 |
129 |
130 | {pages && pages.properties.Name.title[0]?.plain_text}
131 |
132 |
133 |
134 | {pages && (
135 |
139 |
140 | {pages.properties.Name.title[0]?.plain_text}
141 |
142 |
143 |
151 |
152 | {pages.properties.Author.created_by.name} |
153 |
154 |
155 | {format(
156 | new Date(pages.properties.Date.date.start),
157 | 'MMM dd, yyyy',
158 | )}
159 |
160 |
161 |
162 | {pages.properties.Description.rich_text[0]?.plain_text}
163 |
164 |
165 | )}
166 |
167 |
168 |
169 | {blocks &&
170 | // @ts-ignore WIP
171 | blocks.map((block) => {
172 | return {BlockPage(block)}
173 | })}
174 |
175 |
176 | )
177 | }
178 |
179 | export const getStaticPaths: GetStaticPaths = async () => {
180 | const db = await getDatabase(databaseId)
181 | return {
182 | paths: db.map((page) => ({
183 | params: {
184 | id: page.id,
185 | // @ts-ignore WIP
186 | slug: page.properties.Slug.rich_text[0].plain_text,
187 | },
188 | })),
189 | fallback: true,
190 | }
191 | }
192 |
193 | export const getStaticProps: GetStaticProps = async ({ params }) => {
194 | const { slug } = params as ParsedUrlQuery
195 | const database = await getDatabase(databaseId)
196 | const filter = database.filter(
197 | // @ts-ignore WIP
198 | (page) => page.properties.Slug.rich_text[0]?.plain_text === slug,
199 | )
200 | const author = database.slice(0, 1)
201 | const pages = await getPages(filter[0].id)
202 | const blocks = await getBlocks(filter[0].id)
203 | return {
204 | props: {
205 | pages,
206 | blocks,
207 | author,
208 | },
209 | revalidate: 10,
210 | }
211 | }
212 |
213 | export { Pages as default, BlockPage }
214 |
--------------------------------------------------------------------------------
/components/Layout.tsx:
--------------------------------------------------------------------------------
1 | import Head from 'next/head'
2 | import Image from 'next/image'
3 | import Link from 'next/link'
4 | import { useTheme } from 'next-themes'
5 | import Footer, { FooterProps } from './Footer'
6 |
7 | export type LayoutProps = {
8 | home?: boolean
9 | children?: React.ReactNode
10 | src: string
11 | authorDescription?: string
12 | authorSlug?: string
13 | } & FooterProps
14 |
15 | const Layout = ({
16 | children,
17 | home,
18 | src,
19 | authorSlug,
20 | authorDescription,
21 | facebook,
22 | twitter,
23 | linkedin,
24 | github,
25 | authorName,
26 | }: LayoutProps) => {
27 | const { theme, setTheme } = useTheme()
28 |
29 | return (
30 |
31 |
32 |
36 |
40 |
44 |
49 |
50 |
51 |
52 | {home ? (
53 | <>
54 |
119 | >
120 | ) : (
121 | <>
122 |
168 | >
169 | )}
170 |
171 |
172 |
173 | {children}
174 | {!home && (
175 |
176 |
177 |
←
178 |
179 |
180 | )}
181 |
182 |
183 |
190 |
191 | )
192 | }
193 |
194 | export default Layout
195 |
--------------------------------------------------------------------------------