Published on

Configuring NextJS-Tailwind-starter

Authors
  • avatar
    Name
    Jon Chui
    Twitter

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

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

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-spectrum
npm 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:

  1. it is abit too 霸道 for incremental adoption, and
  2. 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:

MDXComponents.js
/* 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.

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:

sandbox.mdx
<font size="+5">
<ruby>
食嘢
<rt>
<Jyutping manual="sik6 je5" />
</rt>
</ruby>
</font>

食嘢

sik6 je5

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.

\[ 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)\]

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.

Formulae & equations

3D molecules