Next.js
Next.js is a React framework with routing, server-side rendering, and a build toolchain. See also React and Tailwind CSS.
Dev
npm install -g react-devtools
Create a project
npx create-next-app@latest project-name
# Set environment variables the same way across platforms
npm install cross-env --save
Change the port
"scripts": {
"dev": "cross-env port=3261 next dev"
}
Or dynamically:
npm run dev -- --port 13001
npm run dev -- -p 13001
Sitemap
npm install next-sitemap -D
Tailwind CSS
Install
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
tailwind.config.js:
module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {},
},
plugins: [],
};
postcss.config.js — point Tailwind at a custom config file if needed:
module.exports = {
plugins: {
// Use ./config.js for the Tailwind config
tailwindcss: { config: './config.js' },
autoprefixer: {},
},
};
Multiple themes
npm install next-themes --save
Dark mode
Emulate the preference in Chrome: Run command ▸ Emulate CSS prefers-color-scheme: light.
tailwind.config.js:
module.exports = {
mode: 'jit',
darkMode: false, // or 'media' (auto) or 'class' (manual)
};
Toggle it at runtime with next-themes:
import { useTheme } from 'next-themes';
// ...
const { theme, setTheme } = useTheme();
setTheme('dark');
Base layer:
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
body {
@apply dark:bg-black;
}
}
Components layer:
@layer components {
@media (prefers-color-scheme: dark) {
[type="checkbox"]:checked {
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='%23262626' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
}
[type="checkbox"] {
@apply focus:ring-0 focus:ring-offset-0;
}
}
}
Environment variables
If a value contains a $, escape it as \$.
Values are layered: if NODE_ENV is development and a variable is defined in
both .env.development.local and .env, the one in .env.development.local
wins.
Exposing variables to the browser
Prefix a variable with NEXT_PUBLIC_ to expose it to the browser:
NEXT_PUBLIC_ANALYTICS_ID=abcdefghijk
Script tags not rendered
next/script must not be wrapped in next/head.
concurrently
Run multiple commands concurrently — like npm run watch-js & npm run watch-less, but more robust.
Upgrading from 12 to 13
npm i next@latest react@latest react-dom@latest eslint-config-next@latest --save
Composing a Next 13 Link with a MUI Link
Next 13 updated the critical next/link component. To combine it with MUI's
Link you must customize the MUI Link, adopt the next/link behavior, and
resolve the href prop naming conflict between the two components.
Applies to MUI 5.x+ and Next.js 13.x+.
Way 1 — legacyBehavior
<NextLink key={title} href={path} passHref legacyBehavior>
<RootLinkStyle active={isActiveRoot}>
<ListItemText disableTypography primary={title} />
</RootLinkStyle>
</NextLink>
Way 2 — the component prop of MUI Link
If you don't need a custom component, use MUI Link's component prop directly.
It has drawbacks:
import { Link as MuiLink } from '@mui/material';
import NextLink from 'next/link';
const Container = () => {
return (
<div>
{/* Prop forwarding: MuiLink automatically gets NextLink's props */}
<MuiLink component={NextLink} prefetch={false} href={'/page1'}>
hello
</MuiLink>
{/* Passing a URL object as href produces a type error */}
<MuiLink
component={NextLink}
prefetch={false}
href={{ pathname: '/page1' }} // type error
>
hello
</MuiLink>
</div>
);
};
export default Container;
Because both MuiLink and NextLink have an href prop, this causes a naming
collision and a type error; the href from MuiLink wins.
Way 3 — rename NextLink's href
Extend NextLink and rename its href prop to avoid the collision:
// components/MyLink.tsx
import { LinkProps, Link as MuiLink } from '@mui/material';
import NextLink, { LinkProps as NextLinkProps } from 'next/link';
// Internal component that avoids the naming collision via _href
export type CustomNextLinkProps = Omit<NextLinkProps, 'href'> & {
_href: NextLinkProps['href'];
};
export const CustomNextLink = ({ _href, ...props }: CustomNextLinkProps) => {
return <NextLink href={_href} {...props} />;
};
// Combine MUI LinkProps with NextLinkProps, remove both href props,
// then define a new href using NextLinkProps
type CombinedLinkProps = LinkProps<typeof NextLink>;
type MyLinkProps = Omit<CombinedLinkProps, 'href'> & {
href: NextLinkProps['href'];
};
const MyLink = ({ href, ...props }: MyLinkProps) => {
return <MuiLink {...props} component={CustomNextLink} _href={href} />;
};
export default MyLink;
CustomNextLink uses _href internally so the MUI/Next href props no longer
collide, and MyLink exposes a single href typed from NextLinkProps.
Usage:
import MyLink from './MyLink';
const Container2 = () => {
return (
<div>
<MyLink
prefetch={false}
scroll={false}
sx={{ color: 'warning.main' }}
href={{ pathname: '/page1' }}
>
hello
</MyLink>
<MyLink
prefetch={false}
scroll={false}
sx={{ color: 'warning.main' }}
href="/page2" // both a string and a URL object are accepted
>
hello
</MyLink>
</div>
);
};
export default Container2;
With styled
To use styled components, wrap the new component as the base:
// components/StyledLink.tsx
import { styled } from '@mui/material/styles';
import MyLink, { MyLinkProps } from './MyLink';
const StyledLink = styled(MyLink)<MyLinkProps>({});
StyledLink.defaultProps = {
color: 'seagreen',
underline: 'hover',
};
export default StyledLink;
Unit test
npm install --save-dev jest babel-jest @babel/preset-env @testing-library/react @testing-library/jest-dom jest-environment-jsdom
Troubleshooting
Exit code 137 (killed)
Usually out of memory. Extend swap or add more RAM.
Disable telemetry
npx next telemetry disable