- Published on
Configuring NextJS-Tailwind-starter
- Authors
- Name
- Jon Chui
Base and Frontmatter
Using Tailwind.css in MDX
By default the unused classes of Tailwind are purged, but the purging only looks through some .js
.ts
files and not the .mdx
. CSS classes that were presented only in mdx
files would then carry a class but have no attached properties. (hat-tip Juan Martin Sesali Maydana)
This was fixed by the following modifications to tailwind.config.js
module.exports = { mode: 'jit', purge: ['./pages/**/*.js', './components/**/*.js', './layouts/**/*.js', './lib/**/*.js', './data/**/*.mdx'],
This makes it really easy to use different layout (e.g., two columns):
Column 1. Some of us love to use parentheses. Unfortunately, some readers ignore anything that appears in parentheses, so don't put important information in parentheses if you can help it.
Column 2. Even for less important information, whenever you're inclined to use parentheses, consider whether they're necessary. Sometimes they are; however, sometimes the sentence or paragraph works just as well if you remove the parentheses and set off the phrase or sentence by using commas, dashes, semicolons, or periods.
Resources
MDX / Next.js
Least technical first
- How I Built My Blog by Josh W Comeau
- MDX Bundler with Next.JS by Adam Laycock
- MDX in Next.js using
mdx-bundler
by Dipesh Wagle mdx-bundler
docs by Kent C Dodds
Testing
Storybook
Component UI
React Spectrum
I tried to add Adobe Spectrum to supplement the Tailwind setup. The instructions is not entirely accurate.
1. Install the npm packages
The highlighted line is for setting up SSR, specifically Next.js.
npm install @adobe/react-spectrumnpm install next-compose-plugins @zeit/next-css next-transpile-modules
2. Next.config.js
The instructions are, however, incorrect, and should be supplemented with the transpiling option as configured here and quoted here.
Together with the bundle analyzer for MDX, the entire next.config.js
looks like the following:
const withPlugins = require('next-compose-plugins')const withTM = require('next-transpile-modules')([ '@adobe/react-spectrum', '@react-spectrum/actiongroup', '@react-spectrum/breadcrumbs', '@react-spectrum/button', '@react-spectrum/buttongroup', '@react-spectrum/checkbox', '@react-spectrum/combobox', '@react-spectrum/dialog', '@react-spectrum/divider', '@react-spectrum/form', '@react-spectrum/icon', '@react-spectrum/illustratedmessage', '@react-spectrum/image', '@react-spectrum/label', '@react-spectrum/layout', '@react-spectrum/link', '@react-spectrum/listbox', '@react-spectrum/menu', '@react-spectrum/meter', '@react-spectrum/numberfield', '@react-spectrum/overlays', '@react-spectrum/picker', '@react-spectrum/progress', '@react-spectrum/provider', '@react-spectrum/radio', '@react-spectrum/slider', '@react-spectrum/searchfield', '@react-spectrum/statuslight', '@react-spectrum/switch', '@react-spectrum/tabs', '@react-spectrum/text', '@react-spectrum/textfield', '@react-spectrum/theme-dark', '@react-spectrum/theme-default', '@react-spectrum/theme-light', '@react-spectrum/tooltip', '@react-spectrum/view', '@react-spectrum/well', '@spectrum-icons/ui', '@spectrum-icons/workflow',])const withBundleAnalyzer = require('@next/bundle-analyzer')({ enabled: process.env.ANALYZE === 'true',})module.exports = withPlugins([withBundleAnalyzer, withTM], { reactStrictMode: true, pageExtensions: ['js', 'jsx', 'md', 'mdx'], eslint: { dirs: ['pages', 'components', 'lib', 'layouts', 'scripts'], }, webpack: (config, { dev, isServer }) => { config.module.rules.push({ test: /\.(png|jpe?g|gif|mp4)$/i, use: [ { loader: 'file-loader', options: { publicPath: '/_next', name: 'static/media/[name].[hash].[ext]', }, }, ], }) config.module.rules.push({ test: /\.svg$/, use: ['@svgr/webpack'], }) if (!dev && !isServer) { // Replace React with Preact only in client production build Object.assign(config.resolve.alias, { react: 'preact/compat', 'react-dom/test-utils': 'preact/test-utils', 'react-dom': 'preact/compat', }) } return config },})
3. _app.js
Now getting next.js
to acknowledge the use of Spectrum across the entire app.
import '@/css/tailwind.css'import { ThemeProvider } from 'next-themes'import { Provider, darkTheme, SSRProvider } from '@adobe/react-spectrum'import Head from 'next/head'import LayoutWrapper from '@/components/LayoutWrapper'export default function App({ Component, pageProps }) { return ( // <ThemeProvider attribute="class"> <SSRProvider> <Provider theme={darkTheme} locale="en-US"> <Head> <meta content="width=device-width, initial-scale=1" name="viewport" /> </Head> <LayoutWrapper> <Component {...pageProps} /> </LayoutWrapper> </Provider> </SSRProvider> )}
There is a mysterious graybox, that from using the Inspector shows that it comes from
._spectrum_eb688 { background-color: var(--spectrum-global-color-gray-100); -webkit-tap-highlight-color: rgba(0, 0, 0, 0);}
...at the end I killed Spectrum:
- it is abit too 霸道 for incremental adoption, and
- it is missing some potentially important components like DatePicker.
As an alternative I looked at BaseUI from Uber, FluentUI from Microsoft, Bootstrap, and AntDesign; finally tried PrimeReact from PrimeFaces.
PrimeReact
To natively use its components in a .mdx
, I encountered a ReactDOM is not defined
error. This was solved by appending ReactDOM
to the MDX bundler:
/* eslint-disable react/display-name */import { useMemo } from 'react'import { getMDXComponent } from 'mdx-bundler/client'import ReactDOM from 'react-dom'import Image from './Image'import CustomLink from './Link'import Pre from './Pre'import Jyutping from './Jyutping'import { MathJax } from 'better-react-mathjax'import ButtonTest from './ButtonTest'export const MDXComponents = { Image, a: CustomLink, pre: Pre, wrapper: ({ components, layout, ...rest }) => { const Layout = require(`../layouts/${layout}`).default return <Layout {...rest} /> }, Jyutping, MathJax, ButtonTest}export const MDXLayoutRenderer = ({ layout, mdxSource, ...rest }) => { const MDXLayout = useMemo(() => getMDXComponent(mdxSource, {ReactDOM} ), [mdxSource]) return <MDXLayout layout={layout} components={MDXComponents} {...rest} />}
The installation was otherwise straightforward.
Features
Custom components
Custom components lives in \components
and can be declared for use in MDXComponents.js
.
// import component (lives in same location as MDXComponents)import Jyutping from './Jyutping'export const MDXComponents = { Image, a: CustomLink, pre: Pre, wrapper: ({ components, layout, ...rest }) => { const Layout = require(`../layouts/${layout}`).default return <Layout {...rest} /> }, Jyutping, // add component}
The Jyutping
component is now universally available in MDX
files:
<font size="+5"> <ruby> 食嘢 <rt> <Jyutping manual="sik6 je5" /> </rt> </ruby></font>
食嘢
Chemistry
Math
The default was KaTeX, interpreted through remark-math
and rehype-katex
. This was extremely convenient for math expressions but I was unable to find out how I can make mhchem
work. I tried loading the mhchem
script both via <script>
(next.js 10) and <Script>
(next/script
) (next.js 11) in various places with no success.
At the end I removed both of the markdown-based interpretations, and rolled in better-react-mathjax
. We wrapped the app (_app.js
) in a <MathJaxContext>
and specified the configurations in there. Note that mhchem
does not seem to be available for MathJax 3+, so I am loading the (less-efficient) MathJax 2.7 instead.
As it stands, the raw input is rather ugly:
<MathJax> {` \\[ x^2 = \\dfrac{y}{e^y} + 5 \\] $$ \\ce{H2O\\liquid{} -> H+\\aq{} + OH^-\\aq{}} $$ \\[\\sum_{n = 100}^{1000}\\left(\\frac{10\\sqrt{n}}{n}\\right)\\] `}</MathJax>
...but the outer compositions can probably be abstracted into inline/standalone <Math>
and <Chem>
components, like in my previous VueJS
setup. That would also be useful in making it dynamic (see CodePen here).
The default renderer is SVG so that it can be SVG exported with browser tools, and the math font is Neo Euler which blends with the aesthetics of Cronos Pro.