Sep 21, 2023

Enforcing coding style with @vercel/style-guide

Learn to enforce coding style with @vercel/style-guide. Uncover effective use of ESLint, Prettier, and TypeScript for improved code quality and consistency.
Enforcing coding style with @vercel/style-guide

What's @vercel/style-guide? Why would I use it?

@vercel/style-guide offers predefined configs of the following tools in a single package.

If you are not sure which ESLint rules or plugins to use (there are A LOT!!!) or wish to enforce code quality but not sure where to start, @vercel/style-guide is a decent drop-in solution.

What's the catch?

@vercel/style-guide is strict, VERY strict. If you are getting used to the default config provisioned by a project CLI, e.g., Next.js and Vite, it might take you some time to adapt to it.

Also, I found some configs aren't making too much sense in the way they are configured. I found myself fighting against those configs instead of enhancing my code quality. Thus, I have overridden some of the configs to how I feel comfortable. The good news to you is, I'm glad to share them with you.

What I'm currently using

FYI, the following are the config files I'm running in my Next.js project. Feel free to copy and/or customize them according to your needs.

.eslintrc.js
const { resolve } = require('node:path');
 
const { JAVASCRIPT_FILES } = require('@vercel/style-guide/eslint/constants');
 
const project = resolve(__dirname, 'tsconfig.json');
 
/** @type {import('eslint').Linter.Config} */
module.exports = {
  root: true,
  extends: [
    require.resolve('@vercel/style-guide/eslint/browser'),
    require.resolve('@vercel/style-guide/eslint/node'),
    require.resolve('@vercel/style-guide/eslint/react'),
    require.resolve('@vercel/style-guide/eslint/next'),
    require.resolve('@vercel/style-guide/eslint/typescript'),
  ],
  parserOptions: { project },
  settings: {
    'import/resolver': { typescript: { project } },
    /**
     * enable MUI Joy components to be checked
     * @see {@link https://github.com/jsx-eslint/eslint-plugin-jsx-a11y?tab=readme-ov-file#configurations}
     */
    'jsx-a11y': {
      polymorphicPropName: 'component',
      components: {
        Button: 'button',
        Icon: 'svg',
        IconButton: 'button',
        Image: 'img',
        Input: 'input',
        Link: 'a',
        List: 'ul',
        ListDivider: 'li',
        ListItem: 'li',
        ListItemButton: 'button',
        NextImage: 'img',
        NextLink: 'a',
        Textarea: 'textarea',
      },
    },
  },
  rules: {
    '@typescript-eslint/explicit-function-return-type': 'off',
    '@typescript-eslint/no-confusing-void-expression': [
      'error',
      { ignoreArrowShorthand: true },
    ],
    '@typescript-eslint/no-shadow': 'off',
    '@typescript-eslint/no-misused-promises': [
      'error',
      { checksVoidReturn: { attributes: false } },
    ],
    '@typescript-eslint/restrict-template-expressions': [
      'error',
      {
        allowAny: false,
        allowBoolean: false,
        allowNullish: false,
        allowRegExp: false,
        allowNever: false,
      },
    ],
    'react/function-component-definition': [
      'warn',
      {
        namedComponents: 'arrow-function',
        unnamedComponents: 'arrow-function',
      },
    ],
    'react/jsx-sort-props': [
      'warn',
      {
        callbacksLast: true,
        shorthandFirst: true,
        multiline: 'last',
        reservedFirst: true,
      },
    ],
    // sort import statements
    'import/order': [
      'warn',
      {
        groups: [
          'builtin',
          'external',
          'internal',
          'parent',
          'sibling',
          'index',
        ],
        'newlines-between': 'always',
        alphabetize: { order: 'asc' },
      },
    ],
    // sort named imports within an import statement
    'sort-imports': ['warn', { ignoreDeclarationSort: true }],
  },
  overrides: [
    /**
     * JS files are using @babel/eslint-parser, so typed linting doesn't work there.
     * @see {@link https://github.com/vercel/style-guide/blob/canary/eslint/_base.js}
     * @see {@link https://typescript-eslint.io/linting/typed-linting#how-can-i-disable-type-aware-linting-for-a-subset-of-files}
     */
    {
      files: JAVASCRIPT_FILES,
      extends: ['plugin:@typescript-eslint/disable-type-checked'],
    },
    // Varies file convention from libraries, e.g. Next.js App Router and Prettier
    // Must use default export
    {
      files: [
        '*.config.{mjs,ts}',
        'src/app/**/{page,layout,not-found,*error,opengraph-image,apple-icon}.tsx',
        'src/app/**/{sitemap,robots}.ts',
        'src/components/emails/*.tsx',
      ],
      rules: {
        'import/no-default-export': 'off',
        'import/prefer-default-export': ['error', { target: 'any' }],
      },
    },
    // module declarations
    {
      files: ['**/*.d.ts'],
      rules: { 'import/no-default-export': 'off' },
    },
  ],
};
tsconfig.json
{
  "extends": "@vercel/style-guide/typescript/node20", // depending on which Node.js version is used
  "compilerOptions": {
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "noEmit": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [{ "name": "next" }],
    "paths": { "@/*": ["./src/*"] }
  },
  "include": [
    "svgr.d.ts",
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    ".next/types/**/*.ts"
  ],
  "exclude": ["node_modules"]
}

For Prettier, I'm not overriding the options in @vercel/style-guide/prettier, but I've extended it to support Prisma.

prettier.config.mjs
import vercelPrettierOptions from '@vercel/style-guide/prettier';
 
/** @type {import('prettier').Config} */
const config = {
  ...vercelPrettierOptions,
  plugins: [...vercelPrettierOptions.plugins, 'prettier-plugin-prisma'],
};
 
export default config;

I'm passionate about 🧠 innovative technologies, 😍 user experience web, and ♿️ accessibility.

Copyright © 2025 KWONG, Matthew Wang Shun