NextJS is powerful: it brings together the best of server-side to the best of client-side. You can build powerful applications written in React, and produce a static website which has all the speed of a regular ol’ CDN-backed website. Despite the benefits, you may sometimes hit some challenges working with both server-side rendering (SSR) and client-side hydration. One of the more common errors is when the server-side and client-side DOM doesn’t match. NextJS might complain:
Expected server HTML to contain a matching <div> in <div>
Warning: Text content did not match. Server: "Log in" Client: "Continue to dashboard"
Additionally, the HTML (in particular, the DOM elements) may not render correctly when viewing the webpage.
Why is this happening?
NextJS uses both server-side and client-side rendering together. The server serves static HTML whenever possible, and the client hydrates it to give full interactivity. If you manipulate the DOM at any time between when the server-side HTML is served and the client-side is rendered, there will be a mismatch and React will not be able to hydrate the DOM successfully. Thus, the first render of any page must match the initial render of the server. This is explained more in-depth in this Github issue. I encountered this issue when relying on cookies. I was doing something like this:
const user = Cookies.get('jwt')
if (user?.id) {
return <span>User logged in!</span>
}
return <span>User not logged in!</span>
Cookies are only accessible in the browser, so when the server provides the static HTML the user would not be logged in, but when the client is rendered, the user would be logged in! Hence, there is a mismatch and NextJS will complain.
How do I fix this?
Put all browser-only code inside useEffect
. This ensures that the client is rendered before the DOM is updated. This means the client-side and server-side DOM will match.
JS Tip:
useEffect
is a React hook that lets you issue side effects - such as updating the DOM - after the page or component is mounted. The above example can be refactored:
const [isUserLoggedIn, setIsUserLoggedIn] = useState(false)
const user = Cookies.get('jwt')
useEffect(() => {
setIsUserLoggedIn(!!user?.id)
}, [user])
if (isUserLoggedIn) {
return <span>User logged in!</span>
}
return <span>User not logged in!</span>
The error goes away!
Paragraph is a blazing-fast, privacy-first, no-frills-attached blogging platform. Sign up and start writing posts just like this one.
ESLint provides checks (and auto-fixes!) for a large list of Javascript and Typescript violations. Paired with a Vim plugin called ALE, these fixes can run on every save.
This blogpost details how to set up ESLint and ALE in Vim, and uses a single usecase - removing unused imports & variables - as an illustration of how powerful these tools are.
Unused Imports & Variables
At the start of a a new React project, I create many different components, and I bootstrap them by copying & pasting component files. This can quickly lead to a large mess of unused imports & variable declarations:
Let’s see how we can remove them - automatically - using the power of ESLint and Vim.
Configuring ESLint
The core solution to this problem is a handy ESLint plugin called eslint-plugin-unused-imports. (Other solutions I found online rely on TSList, which is deprecated, so an ESLint-based solution is preferred).
Let’s install ESLint, as well as the eslint-plugin-unused-imports
plugin:
yarn add -D eslint
yarn add -D eslint-plugin-unused-imports
Now, modify your .eslintrc
to enable the plugin:
// .eslintrc
{
"plugins": ["unused-imports"]
}
Configure your ESLint rules to error on unused imports or unused variables:
// .eslintrc
{
"rules": {
"no-unused-vars": "off", // or "@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"warn",
{ "vars": "all", "varsIgnorePattern": "^_", "args": "after-used", "argsIgnorePattern": "^_" }
]
}
}
Now, we should see unused-imports/no-unused-imports
detecting the unused imports (instead of no-unused-vars, as in the screenshot above):
And running eslint --fix
should automatically remove these imports!
We can do better, though - let’s configure Vim to auto-fix on every save.
Setting up ALE to auto-fix on save
ALE is a plugin for Vim and NeoVim that lints as you type, and auto-fixes syntax and semantic errors. It uses ESLint (and many other extensible tools) underneath to perform the actual fixing logic.
Install ALE by following the instructions and using Pathogen, Vundle, Plug, or one of the other installation options.
After installing ALE, edit your .vimrc
file set two options:
let g:ale_fixers
- Run ESLint as a fixer in your relevant filetypes, which lets ALE tap into ESLint for fixing fileslet g:ale_fix_on_save
- Automatically run all fixers on save
It’ll look something like this:
// .vimrc
let g:ale_fix_on_save = 1
let g:ale_fixers = {
\ 'javascript': ['eslint'],
\ 'typescript': ['eslint'],
\ 'typescriptreact': ['eslint'],
\}
Now, every time you save a js
, ts
or tsx
file, eslint --fix
will automatically run and clean up your imports & unused variables!