├── .babelrc
├── .gitignore
├── README.md
├── _content
├── _templates
│ └── frontmatter.md
├── notes
│ └── a-note.mdx
└── posts
│ ├── code-generation-with-hygen.mdx
│ ├── next-styled-components.mdx
│ └── relative-image-paths-in-mdx-with-remark.mdx
├── next.config.js
├── package-lock.json
├── package.json
├── public
├── favicon.png
├── heart.png
└── ogImage.png
└── src
├── components
├── Example.js
├── Footer
│ ├── index.js
│ └── styles.js
├── GitHub.js
├── Header.js
├── HomePage
│ ├── index.js
│ └── styles.js
├── MDXOverrideComponents.js
├── PostPage
│ ├── index.js
│ └── styles.js
├── PostSnippet
│ ├── index.js
│ └── styles.js
├── SEO.js
├── Twitter.js
├── Width.js
└── index.js
├── pages
├── [slug].js
├── _app.js
├── _document.js
├── about.mdx
├── garden.js
└── index.js
├── site.config.js
├── styles
├── Global.js
└── Theme.js
└── utils
├── contentGlob.js
├── getPathsForContent.js
├── getSinglePost.js
├── index.js
└── renderWithReact.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["next/babel"],
3 | "plugins": [
4 | [
5 | "styled-components",
6 | {
7 | "ssr": true
8 | }
9 | ],
10 | "@babel/plugin-proposal-export-default-from",
11 | [
12 | "module-resolver",
13 | {
14 | "root": [
15 | "./src"
16 | ]
17 | }
18 | ]
19 | ]
20 | }
--------------------------------------------------------------------------------
/.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 |
21 | # debug
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
26 | # local env files
27 | .env.local
28 | .env.development.local
29 | .env.test.local
30 | .env.production.local
31 |
32 | *.code-workspace
33 |
34 | .obsidian
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🌱 Next + MDX Digital Garden Starter
2 |
3 | An opinionated starting point for Digital Garden content authoring.
4 |
5 | Note: This setup does not handle code-splitting. I tried switching to `mdx-bundler` but could not get it to work.
6 |
7 | [](https://vercel.com/import/git?s=https%3A%2F%2Fgithub.com%2FRyanWarner%2Fnext-mdx-digital-garden-starter)
8 |
9 | ## Getting started
10 |
11 | ```
12 | npm install
13 | npm run dev
14 | ```
15 |
16 | ## Use
17 |
18 | - Write MDX in the `content` directory.
19 | - Put components in the `components` directory.
20 | - Use components in MDX (without imports).
21 |
22 | ## Goals
23 |
24 | Statically generated pages from MDX files that are not tied to the filesystem path
25 |
26 | ## How it works
27 |
28 | - `[slug].js` generates static paths using `getStaticPaths` from all `.mdx` files located in the specified directory (`content/` by default).
29 | - The `getStaticProps` NextJS method passes the MDX content including front-matter (parsed with gray-matter) to the `` component to be rendered.
30 | - The index or home page uses similar logic to generate a list of posts sorted by date.
31 |
32 | ## TODO
33 |
34 | - [ ] Support filesystem-based routing as an option
35 | - [ ] Add some batteries-included examples
36 | - [ ] Write a tutorial
37 |
--------------------------------------------------------------------------------
/_content/_templates/frontmatter.md:
--------------------------------------------------------------------------------
1 | ---
2 | title:
3 | description:
4 | date: {{date:MMMM Do YYYY}}
5 | timestamp: {{date}}T{{time}}
6 | isPublished: false
7 | ---
--------------------------------------------------------------------------------
/_content/notes/a-note.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'A note title'
3 | description: 'This MDX file lives in a different directory, exemplifying the ability to organize your content your way.'
4 | date: 'July 20, 2020'
5 | timestamp: 2021-01-15T22:37
6 | ---
7 |
8 | This is a note with front-matter.
--------------------------------------------------------------------------------
/_content/posts/code-generation-with-hygen.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Code generation with Hygen'
3 | description: 'Automate the creation of new components, MDX content, or any other commonly repeated boilerplate.'
4 | date: 'July 20, 2020'
5 | timestamp: 2021-08-15T22:37
6 | ---
7 |
8 | End goal: 2-3 letter command to scaffold a new React component exactly the way you want it.
--------------------------------------------------------------------------------
/_content/posts/next-styled-components.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Next & Styled Components'
3 | description: 'Step by step instructions for installing Styled Components and using it with this starter.'
4 | date: 'July 21, 2020'
5 | timestamp: 2021-09-15T22:37
6 | ---
7 |
8 | ## Installation
9 |
10 | ```bash
11 | npm i styled-components babel-plugin-styled-components
12 | ```
13 |
14 | ## Configuration
15 |
16 | 1. Add the [Babel plugin](https://styled-components.com/docs/tooling#babel-plugin) to your `.babelrc`. Make sure `ssr` is set to `true` because our static page generation happens server side.
17 |
18 | ```
19 | "plugins": [
20 | [
21 | "styled-components",
22 | {
23 | "ssr": true
24 | }
25 | ],
26 | ...
27 | ```
28 |
29 | 2. Configure Styled Components for Server Side Rendering.
30 |
31 | To learn more about how and why we need to do this, see the [Styled Components advanced docs](https://styled-components.com/docs/advanced#server-side-rendering).
32 | We'll be using the [official Next example](https://github.com/vercel/next.js/tree/canary/examples/with-styled-components) as a guide.
33 |
34 | Create a new file, `pages/_document.js`
35 |
36 | ```js
37 | import Document from 'next/document'
38 | import { ServerStyleSheet } from 'styled-components'
39 |
40 | export default class MyDocument extends Document {
41 | static async getInitialProps(ctx) {
42 | const sheet = new ServerStyleSheet()
43 | const originalRenderPage = ctx.renderPage
44 |
45 | try {
46 | ctx.renderPage = () =>
47 | originalRenderPage({
48 | enhanceApp: (App) => (props) =>
49 | sheet.collectStyles(),
50 | })
51 |
52 | const initialProps = await Document.getInitialProps(ctx)
53 | return {
54 | ...initialProps,
55 | styles: (
56 | <>
57 | {initialProps.styles}
58 | {sheet.getStyleElement()}
59 | >
60 | ),
61 | }
62 | } finally {
63 | sheet.seal()
64 | }
65 | }
66 | }
67 | ```
68 |
69 | 3. Start styling 🎨
70 |
71 | You're all set to start using Styled Components in your NextJS app!
--------------------------------------------------------------------------------
/_content/posts/relative-image-paths-in-mdx-with-remark.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Relative image paths in MDX with Remark'
3 | description: 'Tame your images in MDX with the help of Remark.'
4 | date: 'July 22, 2020'
5 | timestamp: 2021-02-15T22:37
6 | ---
7 |
8 |
9 |
10 | This image lives inside the public/ dir. It could be nice to use next/image for this.
11 | 
12 |
13 | It indicates a synchronic distortion in the areas emanating triolic waves. The cerebellum, the cerebral cortex, the brain stem, the entire nervous system has been depleted of electrochemical energy. Any device like that would produce high levels of triolic waves. These walls have undergone some kind of selective molecular polarization. I haven't determined if our phaser energy can generate a stable field. We could alter the photons with phase discriminators.
14 |
15 | I have reset the sensors to scan for frequencies outside the usual range. By emitting harmonic vibrations to shatter the lattices. We will monitor and adjust the frequency of the resonators. He has this ability of instantly interpreting and extrapolating any verbal communication he hears. It may be due to the envelope over the structure, causing hydrogen-carbon helix patterns throughout. I'm comparing the molecular integrity of that bubble against our phasers.
16 |
17 | Communication is not possible. The shuttle has no power. Using the gravitational pull of a star to slingshot back in time? We are going to Starbase Montgomery for Engineering consultations prompted by minor read-out anomalies. Probes have recorded unusual levels of geological activity in all five planetary systems. Assemble a team. Look at records of the Drema quadrant. Would these scans detect artificial transmissions as well as natural signals?
18 |
19 | Sensors indicate human life forms 30 meters below the planet's surface. Stellar flares are increasing in magnitude and frequency. Set course for Rhomboid Dronegar 006, warp seven. There's no evidence of an advanced communication network. Total guidance system failure, with less than 24 hours' reserve power. Shield effectiveness has been reduced 12 percent. We have covered the area in a spherical pattern which a ship without warp drive could cross in the given time.
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const withMDX = require('@next/mdx')({
2 | extension: /\.(md|mdx)$/
3 | })
4 |
5 | module.exports = (nextConfig = {}) => {
6 | return Object.assign({}, nextConfig, withMDX({
7 | pageExtensions: ['js', 'jsx', 'mdx'],
8 | }))
9 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next-mdx-digital-garden-starter",
3 | "version": "1.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next-remote-watch _content",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "prettier-standard --lint 'src/**/*.{js,jsx,json,md}'"
10 | },
11 | "dependencies": {
12 | "@mdx-js/loader": "^2.0.0-next.9",
13 | "@mdx-js/runtime": "^2.0.0-next.9",
14 | "@mdx-js/tag": "^0.20.3",
15 | "@next/mdx": "11.0.1",
16 | "fast-glob": "3.2.7",
17 | "gray-matter": "4.0.3",
18 | "next": "11.0.1",
19 | "prop-types": "15.7.2",
20 | "react": "17.0.2",
21 | "react-dom": "17.0.2",
22 | "styled-components": "5.3.0"
23 | },
24 | "devDependencies": {
25 | "@babel/core": "^7.14.6",
26 | "@babel/plugin-proposal-export-default-from": "7.14.5",
27 | "@babel/plugin-proposal-object-rest-spread": "^7.14.7",
28 | "@babel/plugin-transform-react-jsx": "^7.14.5",
29 | "babel-plugin-module-resolver": "4.1.0",
30 | "babel-plugin-remove-export-keywords": "1.6.22",
31 | "babel-plugin-styled-components": "1.13.2",
32 | "next-remote-watch": "^1.0.0",
33 | "prettier-standard": "16.4.1"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RyanWarner/next-mdx-digital-garden-starter/01d5dad4aaf9e926238d3c62b5eb7c73b753a7dd/public/favicon.png
--------------------------------------------------------------------------------
/public/heart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RyanWarner/next-mdx-digital-garden-starter/01d5dad4aaf9e926238d3c62b5eb7c73b753a7dd/public/heart.png
--------------------------------------------------------------------------------
/public/ogImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RyanWarner/next-mdx-digital-garden-starter/01d5dad4aaf9e926238d3c62b5eb7c73b753a7dd/public/ogImage.png
--------------------------------------------------------------------------------
/src/components/Example.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | const ExampleComponent = styled.div`
4 | color: green;
5 | `
6 |
7 | const Example = props => (
8 | This is an example component
9 | )
10 |
11 | export default Example
12 |
--------------------------------------------------------------------------------
/src/components/Footer/index.js:
--------------------------------------------------------------------------------
1 | import * as S from './styles'
2 | import { Width } from 'components'
3 | import siteConfig from 'site.config'
4 |
5 | const Footer = props => (
6 |
7 |
8 |
9 | © {new Date().getFullYear()} {siteConfig.author}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | )
22 |
23 | export default Footer
24 |
--------------------------------------------------------------------------------
/src/components/Footer/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | import Twitter from '../Twitter'
4 | import GitHub from '../GitHub'
5 |
6 | export const Footer = styled.footer`
7 | height: 70px;
8 | display: flex;
9 | align-items: center;
10 | width: 100%;
11 | border-top: 1px solid ${props => props.theme.rule};
12 | `
13 |
14 | export const Copyright = styled.p`
15 | display: flex;
16 | align-items: center;
17 | `
18 |
19 | export const Social = styled.div`
20 | display: flex;
21 | align-items: center;
22 | margin-left: auto;
23 | `
24 |
25 | export const StyledGitHub = styled(GitHub)`
26 | margin-left: 12px;
27 | `
28 |
29 | export const StyledTwitter = styled(Twitter)``
30 |
31 | export const A = styled.a`
32 | &:hover {
33 | path {
34 | fill: ${props => props.theme.green10};
35 | }
36 | }
37 | `
38 |
--------------------------------------------------------------------------------
/src/components/GitHub.js:
--------------------------------------------------------------------------------
1 | const GitHub = props => (
2 |
17 | )
18 |
19 | export default GitHub
20 |
--------------------------------------------------------------------------------
/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import Link from 'next/link'
2 | import siteConfig from 'site.config'
3 | import styled from 'styled-components'
4 |
5 | import { Width } from 'components'
6 |
7 | const Nav = styled.nav`
8 | height: 70px;
9 | display: flex;
10 | align-items: center;
11 | width: 100%;
12 | `
13 |
14 | const Wordmark = styled.div`
15 | display: flex;
16 | align-items: center;
17 | `
18 |
19 | const Logo = styled.span`
20 | margin-right: 8px;
21 | font-size: 20px;
22 | `
23 |
24 | const Name = styled.span`
25 | font-weight: bold;
26 | `
27 |
28 | const NavItems = styled.ul`
29 | margin-left: auto;
30 | display: flex;
31 | `
32 |
33 | export const A = styled.a`
34 | text-decoration: none;
35 | color: ${props => props.theme.text10};
36 |
37 | &:hover {
38 | color: ${props => props.theme.green10};
39 | }
40 | `
41 |
42 | export const NavItem = styled.li`
43 | margin-left: 24px;
44 | list-style-type: none;
45 | `
46 |
47 | const Header = props => (
48 |
49 |
71 |
72 | )
73 |
74 | export default Header
75 |
--------------------------------------------------------------------------------
/src/components/HomePage/index.js:
--------------------------------------------------------------------------------
1 | import { SEO, PostSnippet, GitHub, Header } from 'components'
2 | import * as S from './styles'
3 |
4 | const githubUrl =
5 | 'https://github.com/RyanWarner/next-mdx-digital-garden-starter'
6 |
7 | export default function HomePage ({ allMdx }) {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 | NextJS + MDX
15 |
16 | Digital Garden Starter
17 |
18 |
19 |
20 |
21 | Create top level routes from .mdx files organized however you want.
22 |
23 |
24 | Statically generated routes using Next’s `getStaticPaths`.
25 |
26 | Supports frontmatter (thanks to gray-matter).
27 |
28 |
29 |
30 |
31 | View source on GitHub
32 |
33 |
34 | Featured posts
35 |
36 | {allMdx.map(item => (
37 |
38 |
39 |
40 | ))}
41 |
42 |
43 |
44 |
45 | )
46 | }
47 |
--------------------------------------------------------------------------------
/src/components/HomePage/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 | import { Footer } from 'components'
3 |
4 | export const Wrap = styled.div`
5 | display: flex;
6 | flex-direction: column;
7 | align-items: center;
8 | padding: 0 30px;
9 | min-height: 100vh;
10 | box-sizing: border-box;
11 | `
12 |
13 | export const StyledFooter = styled(Footer)`
14 | margin-top: auto;
15 | padding-top: 40px;
16 | `
17 |
18 | export const Main = styled.main`
19 | max-width: 700px;
20 | padding: 0 30px;
21 | width: 100%;
22 | margin-top: 60px;
23 | position: relative;
24 | display: flex;
25 | flex-direction: column;
26 | `
27 |
28 | export const H1 = styled.h1`
29 | font-size: 50px;
30 | margin-bottom: 20px;
31 | `
32 |
33 | export const GitHubButton = styled.a`
34 | border-radius: 4px;
35 | border: 1px solid black;
36 | padding: 10px 12px;
37 | color: black;
38 | text-decoration: none;
39 | margin-top: 30px;
40 | display: grid;
41 | gap: 14px;
42 | grid-template-columns: auto auto;
43 | align-self: flex-start;
44 | `
45 |
46 | export const H2 = styled.h2`
47 | letter-spacing: 0.1em;
48 | text-transform: uppercase;
49 | font-size: 14px;
50 | margin-top: 100px;
51 | color: ${props => props.theme.text20};
52 | `
53 |
54 | export const FeatureList = styled.ul`
55 | margin: 8px 0 0 0;
56 | padding: 0 0 0 30px;
57 | `
58 |
59 | export const ListItem = styled.li`
60 | font-size: 21px;
61 | margin-bottom: 16px;
62 | color: ${props => props.theme.text20};
63 | `
64 |
65 | export const PostList = styled.ul`
66 | margin: 22px 0 0 0;
67 | padding: 0;
68 | list-style-type: none;
69 | `
70 |
71 | export const PostListItem = styled.li`
72 | margin-bottom: 30px;
73 | `
74 |
--------------------------------------------------------------------------------
/src/components/MDXOverrideComponents.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const p = styled.p`
4 | font-size: 18px;
5 | line-height: 180%;
6 | margin-bottom: 3rem;
7 | `
8 |
--------------------------------------------------------------------------------
/src/components/PostPage/index.js:
--------------------------------------------------------------------------------
1 | import * as S from './styles'
2 | import { SEO, Header } from 'components'
3 |
4 | const PostPage = ({ frontmatter, mdxHtml }) => {
5 | return (
6 | <>
7 |
8 |
9 |
10 |
11 | {frontmatter.title}
12 | {frontmatter.description}
13 |
14 |
15 |
16 | >
17 | )
18 | }
19 |
20 | export default PostPage
21 |
--------------------------------------------------------------------------------
/src/components/PostPage/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const Wrap = styled.div`
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | padding: 0 30px;
8 | `
9 |
10 | export const Main = styled.main`
11 | max-width: 700px;
12 | width: 100%;
13 | `
14 |
15 | export const H1 = styled.h1`
16 | color: green;
17 | `
18 |
--------------------------------------------------------------------------------
/src/components/PostSnippet/index.js:
--------------------------------------------------------------------------------
1 | import Link from 'next/link'
2 |
3 | import * as S from './styles'
4 |
5 | export default function PostSnippet ({ slug, frontmatter }) {
6 | const href = `/${slug}`
7 |
8 | return (
9 | <>
10 |
11 |
18 | {frontmatter.title}
19 |
20 |
21 | {frontmatter.description}
22 | >
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/PostSnippet/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const Description = styled.p`
4 | font-size: 14px;
5 | margin: 6px 0 0 0;
6 | color: ${props => props.theme.text20};
7 | max-width: 400px;
8 | line-height: 160%;
9 | `
10 |
--------------------------------------------------------------------------------
/src/components/SEO.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head'
2 |
3 | import siteConfig from 'site.config'
4 |
5 | export default function SEO ({
6 | title,
7 | description,
8 | twitterHandle,
9 | favicon,
10 | ogImage,
11 | url
12 | }) {
13 | return (
14 |
15 | {title}
16 |
17 |
18 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | )
35 | }
36 |
37 | SEO.defaultProps = {
38 | ...siteConfig
39 | }
40 |
--------------------------------------------------------------------------------
/src/components/Twitter.js:
--------------------------------------------------------------------------------
1 | const Twitter = props => (
2 |
15 | )
16 |
17 | export default Twitter
18 |
--------------------------------------------------------------------------------
/src/components/Width.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | const MaxWidth = styled.div`
4 | max-width: 700px;
5 | width: 100%;
6 | `
7 |
8 | const CenterContainer = styled.div`
9 | display: flex;
10 | justify-content: center;
11 | width: 100%;
12 | `
13 |
14 | const Width = ({ children, ...rest }) => (
15 |
16 | {children}
17 |
18 | )
19 |
20 | export default Width
21 |
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 | export SEO from './SEO'
2 | export Width from './Width'
3 | export Header from './Header'
4 | export Footer from './Footer'
5 | export Example from './Example'
6 | export PostSnippet from './PostSnippet'
7 | export GitHub from './GitHub'
8 | export Twitter from './Twitter'
9 | export HomePage from './HomePage'
10 | export PostPage from './PostPage'
11 | export * from './MDXOverrideComponents'
12 |
--------------------------------------------------------------------------------
/src/pages/[slug].js:
--------------------------------------------------------------------------------
1 | import * as components from 'components'
2 | import getSinglePost from 'utils/getSinglePost'
3 | import getPathsForContent from 'utils/getPathsForContent'
4 |
5 | export async function getStaticPaths() {
6 | const paths = getPathsForContent()
7 |
8 | return {
9 | paths,
10 | fallback: false
11 | }
12 | }
13 |
14 | export async function getStaticProps({ params: { slug } }) {
15 | const { mdxHtml, frontmatter } = await getSinglePost(slug)
16 |
17 | return {
18 | props: {
19 | mdxHtml,
20 | frontmatter
21 | }
22 | }
23 | }
24 |
25 | export default components.PostPage
26 |
--------------------------------------------------------------------------------
/src/pages/_app.js:
--------------------------------------------------------------------------------
1 | import App from 'next/app'
2 | import { ThemeProvider } from 'styled-components'
3 |
4 | import { GlobalStyle } from '../styles/Global'
5 | import Theme from 'styles/Theme'
6 |
7 | export default class MyApp extends App {
8 | render () {
9 | const { Component, pageProps } = this.props
10 | return (
11 |
12 |
13 |
14 |
15 | )
16 | }
17 | }
--------------------------------------------------------------------------------
/src/pages/_document.js:
--------------------------------------------------------------------------------
1 | import Document from 'next/document'
2 | import { ServerStyleSheet } from 'styled-components'
3 | import { MDXProvider } from '@mdx-js/react'
4 | import glob from 'fast-glob'
5 | import fs from 'fs'
6 | import MDXRuntime from '@mdx-js/runtime'
7 |
8 | import * as components from 'components'
9 | import contentGlob from 'utils/contentGlob'
10 |
11 | /**
12 | * We have to include all of our MDX documents in the
13 | * collectStyles call for Styled Components to work properly.
14 | *
15 | * This could potentially create performance issues on large
16 | * sites with lots of bespoke components. Is there a better way?
17 | */
18 | export const getAllMdx = async () => {
19 | const files = glob.sync(contentGlob)
20 | const mdxDocs = []
21 |
22 | for (const file of files) {
23 | const mdxSource = fs.readFileSync(file)
24 |
25 | mdxDocs.push()
26 | }
27 |
28 | return () => (
29 |
30 | {mdxDocs.map(MdxDoc => {
31 | return MdxDoc
32 | })}
33 |
34 | )
35 | }
36 |
37 | export default class MyDocument extends Document {
38 | static async getInitialProps (ctx) {
39 | const sheet = new ServerStyleSheet()
40 | const originalRenderPage = ctx.renderPage
41 |
42 | const MDX = await getAllMdx()
43 |
44 | try {
45 | ctx.renderPage = () =>
46 | originalRenderPage({
47 | enhanceApp: App => props =>
48 | sheet.collectStyles(
49 | <>
50 |
51 | {MDX && }
52 | >
53 | )
54 | })
55 |
56 | const initialProps = await Document.getInitialProps(ctx)
57 | return {
58 | ...initialProps,
59 | styles: (
60 | <>
61 | {initialProps.styles}
62 | {sheet.getStyleElement()}
63 | >
64 | )
65 | }
66 | } finally {
67 | sheet.seal()
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/pages/about.mdx:
--------------------------------------------------------------------------------
1 | # MDX powered About page!
2 |
3 | Here lies some MDX.
4 |
5 | ```
6 | const code = {}
7 | ```
--------------------------------------------------------------------------------
/src/pages/garden.js:
--------------------------------------------------------------------------------
1 | import { SEO, Header } from 'components'
2 |
3 | const Garden = () => {
4 | return (
5 |
6 |
7 |
8 |
Garden
9 |
This is where we should put a list of our garden posts.
10 |
11 | )
12 | }
13 |
14 | export default Garden
--------------------------------------------------------------------------------
/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import matter from 'gray-matter'
3 | import glob from 'fast-glob'
4 |
5 | import { HomePage } from 'components'
6 | import contentGlob from 'utils/contentGlob'
7 |
8 | export default HomePage
9 |
10 | export async function getStaticProps () {
11 | const files = glob.sync(contentGlob)
12 |
13 | const allMdx = files.map(file => {
14 | const split = file.split('/')
15 | const filename = split[split.length - 1]
16 | const slug = filename.replace('.mdx', '').replace('.md', '')
17 |
18 | const mdxSource = fs.readFileSync(file)
19 | const { data } = matter(mdxSource)
20 |
21 | return {
22 | slug,
23 | frontmatter: data
24 | }
25 | })
26 |
27 | const orderedByDate = allMdx.sort((a, b) => {
28 | return new Date(b.frontmatter.timestamp) - new Date(a.frontmatter.timestamp)
29 | })
30 |
31 | return {
32 | props: {
33 | allMdx: orderedByDate
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/site.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | title: 'NextJS MDX Digital Garden Starter',
3 | description:
4 | 'An opinionated starting point for authoring interactive content using MDX and NextJS',
5 | twitterHandle: '@RyanWarnerCodes',
6 | githubUsername: 'RyanWarner',
7 | author: 'Your Name',
8 | favicon: '/favicon.png',
9 | ogImage: '/ogImage.png',
10 | url: 'https://next-mdx.warner.codes'
11 | }
12 |
--------------------------------------------------------------------------------
/src/styles/Global.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components'
2 |
3 | export const GlobalStyle = createGlobalStyle`
4 | html {
5 | margin: 0;
6 | padding: 0;
7 | max-width: 100vw;
8 | background-color: ${props => props.theme.background};
9 | color: ${props => props.theme.text10};
10 | font-family: ${props => props.theme.fontFamily};
11 | }
12 |
13 | body {
14 | min-height: 100vh;
15 | margin: 0;
16 | padding: 0;
17 | max-width: 100vw;
18 | -webkit-font-smoothing: antialiased;
19 | -moz-osx-font-smoothing: grayscale;
20 | }
21 |
22 | div {
23 | -webkit-tap-highlight-color: rgba(255, 255, 255, 0);
24 | }
25 |
26 | ::selection {
27 | background: ${props => props.theme.highlightBackground};
28 | color: ${props => props.theme.text10};
29 | }
30 | `
31 |
--------------------------------------------------------------------------------
/src/styles/Theme.js:
--------------------------------------------------------------------------------
1 | const typeTokens = {
2 | fontFamily: 'Lato, sans-serif'
3 | }
4 |
5 | export default {
6 | light: {
7 | ...typeTokens,
8 | background: '#FBFBF9',
9 | text10: '#1E1E1B',
10 | text20: '#696966',
11 | rule: 'rgba(0, 0, 0, 0.1)',
12 | green10: '#79BB40',
13 | highlightBackground: '#ABEF70'
14 | },
15 | dark: {
16 | ...typeTokens,
17 | background: '#171716',
18 | text10: '#FBFBF9',
19 | text20: '#9C9C96',
20 | rule: 'rgba(255, 255, 255, 0.1)',
21 | green10: '#79BB40',
22 | highlightBackground: '#ABEF70'
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/utils/contentGlob.js:
--------------------------------------------------------------------------------
1 | const contentGlob = ['_content/**/*.{md,mdx}', '!_content/_templates']
2 | export default contentGlob
--------------------------------------------------------------------------------
/src/utils/getPathsForContent.js:
--------------------------------------------------------------------------------
1 | import glob from 'fast-glob'
2 |
3 | import contentGlob from './contentGlob'
4 |
5 | const getSinglePost = () => {
6 | const files = glob.sync(contentGlob)
7 |
8 | const paths = files.map(file => {
9 | const split = file.split('/')
10 | const filename = split[split.length - 1]
11 | const slug = filename.replace('.mdx', '').replace('.md', '')
12 |
13 | return {
14 | params: {
15 | slug
16 | }
17 | }
18 | })
19 |
20 | return paths
21 | }
22 |
23 | export default getSinglePost
--------------------------------------------------------------------------------
/src/utils/getSinglePost.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import matter from 'gray-matter'
3 | import glob from 'fast-glob'
4 |
5 | import * as components from 'components'
6 | import { renderWithReact } from 'utils/renderWithReact'
7 | import contentGlob from './contentGlob'
8 |
9 | const getSinglePost = async (slug) => {
10 | const files = glob.sync(contentGlob)
11 |
12 | const fullPath = files.filter(item => {
13 | const split = item.split('/')
14 | const filename = split[split.length - 1]
15 | return filename.replace('.mdx', '').replace('.md', '') === slug
16 | })[0]
17 |
18 | const mdxSource = fs.readFileSync(fullPath)
19 | const { content, data } = matter(mdxSource)
20 |
21 | if (!fullPath) {
22 | console.warn('No MDX file found for slug')
23 | }
24 |
25 | const mdxHtml = await renderWithReact(content, { components })
26 |
27 | return {
28 | mdxHtml,
29 | frontmatter: data || {}
30 | }
31 | }
32 |
33 | export default getSinglePost
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | import glob from 'fast-glob'
2 |
3 | import contentGlob from './contentGlob'
4 |
5 | export const getSlugForFilePath = path => {
6 | const split = path.split('/')
7 | const filename = split[split.length - 1]
8 | const slug = filename.replace('.mdx', '').replace('.md', '')
9 | return slug
10 | }
11 |
12 | export const getContent = () => {
13 | const files = glob.sync(contentGlob)
14 |
15 | return files.map(item => ({
16 | filepath: item,
17 | slug: getSlugForFilePath(item)
18 | }))
19 | }
20 |
21 | const content = getContent()
22 |
23 | export const getFilePathForSlug = slug => {
24 | return content.filter(item => item.slug === slug)[0].filepath
25 | }
26 |
--------------------------------------------------------------------------------
/src/utils/renderWithReact.js:
--------------------------------------------------------------------------------
1 | /* Created from: https://github.com/mdx-js/mdx/blob/master/packages/test-util/index.js */
2 | const babel = require('@babel/core')
3 | const { MDXProvider, mdx: createElement } = require('@mdx-js/react')
4 | const React = require('react')
5 | const { renderToStaticMarkup } = require('react-dom/server')
6 |
7 | const mdx = require('@mdx-js/mdx')
8 |
9 | const transform = code =>
10 | babel.transform(code, {
11 | plugins: [
12 | '@babel/plugin-transform-react-jsx',
13 | '@babel/plugin-proposal-object-rest-spread',
14 | 'babel-plugin-remove-export-keywords'
15 | ]
16 | }).code
17 |
18 | const renderWithReact = async (mdxCode, { components } = {}) => {
19 | const jsx = await mdx(mdxCode, { skipExport: true })
20 | const code = transform(jsx)
21 | const scope = { mdx: createElement }
22 |
23 | const fn = new Function( // eslint-disable-line no-new-func
24 | 'React',
25 | ...Object.keys(scope),
26 | `${code}; return React.createElement(MDXContent)`
27 | )
28 |
29 | const element = fn(React, ...Object.values(scope))
30 |
31 | const elementWithProvider = React.createElement(
32 | MDXProvider,
33 | { components },
34 | element
35 | )
36 |
37 | return renderToStaticMarkup(elementWithProvider)
38 | }
39 |
40 | module.exports.renderWithReact = renderWithReact
41 |
--------------------------------------------------------------------------------