Managing a single project is draining enough; adding a monorepo to the mix can
make it much worse. I see the same pattern all the time: the first app has a
clean .eslintrc, the second app just copies it, and the third app modifies a
single rule. Fast forward three months, and you're stuck debugging why a build
failed in the admin app while the others passed, hunting for configuration
errors that shouldn't even be there.
Personally, I like to treat ESLint and Prettier like actual products. They have their own versions, they are consumed explicitly, and when something needs to change, you change it in one place.
My strategy is to wrap these configurations into their own packages within the
workspace—for example, @acme/config-prettier. The contents are very thin,
mostly just exporting base settings. The apps in the monorepo then just import
this package. I do the same for ESLint: create one package that exports a
configuration object (or a flat config array) that other apps can extend.
Of course, you shouldn't throw everything into a shared package. There has to be a limit.
In my view, only the fundamentals belong in a shared package—things like
ecmaVersion, import rules, or TypeScript baselines. Highly specific framework
stuff, like Next.js rules or .next folder ignore patterns, should stay at the
individual app level.
There is one rule I won't compromise on: if you're using both, "prettier" must
be in your ESLint extends (via eslint-config-prettier). I don't want two
different tools fighting over a semicolon. Let Prettier handle the aesthetics
and formatting, and let ESLint focus on the logic and code correctness.
Another common headache is sloppy internal imports. Sometimes an app will import
directly from a deep path (like @acme/ui/src/button.tsx) instead of using the
public exports. This usually breaks our linting assumptions. Setting up
import/no-extraneous-dependencies with the correct packageDir helps a lot
here to avoid a flood of false positives.
When it comes to maintenance, I prefer simple consistency over a massive list of rules. If your configuration is too rigid with styling rules, people will eventually ignore them or start modifying them secretly. I’d rather focus on rules that actually prevent bugs. If you really need an exception, use targeted overrides at the app level instead of creating a whole new branch of configuration.
One more crucial thing: keep your local environment and CI in sync. The configuration package running on my laptop must be the same one running on the server. Using different ESLint versions within the same monorepo is a guaranteed way to make "but it works on my machine" a regular topic in your weekly meetings.
This approach definitely has its downsides, mostly because it requires extra time upfront to set up the package structure. But for me, it makes much more sense than constantly fighting configuration drift across every single app.
That's the shape of it for me.