A Next.js starter that dropped ESLint and kept the opinions to a minimum
Non-opinionated TypeScript starter for Next.js 16. All the tools you need to build your next project ⚡️
At a glance
- What is it?
- The TypeScript Next.js starter by João Pedro Schmitz has moved from ESLint and Prettier to Oxlint and Oxfmt, and its release history records that migration in plain sight.
- Who is it for?
- What this starter actually offers is a set of decisions already made and a way to argue with them. The defaults cover the things a new Next.js project ends up needing anyway: typed environment variables with a schema split between client and server, a typed redirect array, a Content Security Policy in the config file rather than in middleware, and hooks that keep commit messages conventional.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 7, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A template you create rather than a repository you clone
The recommended entry point is Create Next App with the example flag, which points the generator at the GitHub URL so you get a fresh project rather than a copy of the template's history:
# pnpm
pnpm create next-app -e https://github.com/jpedroschmitz/typescript-nextjs-starter
# yarn
yarn create next-app -e https://github.com/jpedroschmitz/typescript-nextjs-starter
# npm
npx create-next-app -e https://github.com/jpedroschmitz/typescript-nextjs-starterThree variants appear because the generator has three names, and the choice of package manager is a template-level decision that you can undo. The README documents how: delete pnpm-lock.yaml, install with Yarn or npm instead, then update the CI workflow and the Husky hooks to use the same commands. There is a specific warning attached to Yarn on Windows, where Husky hooks can fail and the Husky troubleshooting documentation is the fix.
Once running, `pnpm dev` starts the app and it appears on localhost:3000. The repository has 1,431 stars, 163 forks and only 9 open issues, under the MIT licence, with the last push on 2026-09-18 and a documentation site at next-ts.joaopedro.dev. The low open-issue count is consistent with how the project is used: most people generate a project from it and then leave, so issues come from people who intend to contribute back.
Oxlint and Oxfmt replaced the usual pair in version 2.5.0
The release history is short and legible, which makes it a good way to see how the template tracks upstream. Version 2.3.2 landed on 2025-10-22 and moved to Next.js 16. Version 2.4.0 followed on 2025-12-22 with Next.js 16.1, listing next typegen, experimental build caching, an experimental bundle analyzer, and a new ESLint config based on usage in different codebases. Version 2.5.0 on 2026-01-27 then migrated to Oxlint and Oxfmt, and after that entry the release notes are almost entirely Renovate commits bumping zod, commitlint and typescript-eslint.
That sequence has a small irony in it. The release that refined the ESLint configuration was superseded four weeks later by a migration away from ESLint. Nothing was wasted, since knowing which rules a real codebase needed is what informed the new setup, but the direction of travel is clear: the template now treats Oxlint as the linter and Oxfmt as the formatter.
The package scripts show the concrete surface, including flags that indicate how much each tool is doing:
"lint": "oxlint --type-aware",
"lint:fix": "oxlint --fix --type-aware",
"format": "oxfmt --write",
"format:check": "oxfmt --check",
"format:ci": "oxfmt --list-different .",Type-aware linting is the notable one, and it is enabled through a separate dev dependency, oxlint-tsgolint, so linting that understands TypeScript types does not require running the TypeScript compiler itself as part of the lint step. Configuration lives in `.oxlintrc.json` and `.oxfmtrc.json`, both at the root, alongside an `.editorconfig` for editors that do not read either of them.
The staged-files configuration runs both tools on anything matching the usual JavaScript and TypeScript extensions:
"*.{js,jsx,ts,tsx,mjs,cjs}": [
"oxlint --fix",
"oxfmt --write --no-error-on-unmatched-pattern"
]Three hooks, and the one that is off by default
Husky is wired in through a postinstall script rather than a manual setup step, so the hooks exist as soon as dependencies install. Three of them are configured.
The commit-msg hook runs Commitlint against the conventional commit format, and its config sits in `.commitlintrc.json` next to the matching `@commitlint/config-conventional` dev dependency. The post-merge hook runs pnpm install when pnpm-lock.yaml changed, which is the behaviour that keeps a merge from main silently leaving you on stale dependencies.
The pre-commit hook runs lint-staged, and this one is disabled by default. The README states the reason plainly: most developers do not want it. That is an unusual choice for a starter that otherwise leans on automation, and it is the one place where the template has an opinion you may want to reverse. Turning it on means every commit formats and lints only the staged files, and the flip side is a slower commit for a codebase with many small files.
Renovate handles the dependency updates, configured in `renovate.json`. Given how much of the recent release history consists of Renovate pull requests, this is the component keeping the template's Next.js and React versions current between releases, and it is why generating from the template beats maintaining a fork: a fork stops receiving those bumps.
The CI side is a pull request workflow under `.github/` that runs type check and linters, which is the same pair a human would run locally, just automated.
Typed environment variables, typed redirects, a default CSP
The three features that do real work after you start writing code are the ones with a file you have to edit.
Environment variables are managed with T3 Env, so you create a `.env.local` in the project root and add your variables to a schema rather than reading process.env by hand. The schema is split across two files: `./src/lib/env/client.ts` for values that reach the browser and `./src/lib/env/server.ts` for values that must not. The package manifest pins `@t3-oss/env-nextjs` at 0.13.11 and `zod` at 4.5.4, so the validation layer is Zod underneath, which means the split is enforced rather than merely documented.
Redirects live in `./redirects.ts` as a typed array. Because the array is typed, the properties get autocompletion, which is a small quality of life detail that pays off every time you add one.
The Content Security Policy is implemented in `next.config.ts` rather than in middleware, and ships with a default minimal policy that you customise. The README is clear about the framing: it is a foundation to build on, not a finished policy. Since it is a header set in config, it applies to the whole application and does not depend on a runtime file existing, which makes it easy to forget that it is there and easy to weaken by accident when you add a third-party script.
Path mapping is the last of the small conveniences, preconfigured in TypeScript so imports use the `@` prefix:
import { Button } from '@/components/Button';
// To import images or other files from the public folder
import avatar from '@/public/avatar.png';The second line is the one people underestimate. Importing an asset from the public folder by path, with the import resolved at build time, avoids the string-path lookup that usually follows an asset around when someone renames a file.
Two places where the manifest and the README disagree
The requirements section of the README asks for Node.js 24 or newer and pnpm 10. The package manifest, which is the file that actually runs, declares `"packageManager": "[email protected]"`. Both statements can be true at once, since a major version of 12 satisfies nothing about a documented floor of 10 if you read the floor as the toolchain the project was written against, and if your package manager honours the packageManager field through corepack it will install 12.3.2 regardless of what the README says.
The practical effect is small but real: the README understates the requirement, so someone on pnpm 10 who ignores the manifest can install dependencies, hit something that pnpm 12 handles differently, and lose time before realising the cause. The `.nvmrc` file and `.npmrc` at the root are where the real Node and package manager expectations live, and both are easier to consult than the README prose.
There is a second, milder inconsistency: the manifest version is 1.0.0 while the published releases are numbered 2.5.0, 2.4.0 and 2.3.2. For a template marked private this has no effect on anything, since the version field is never published, but it does mean you cannot use the version to tell which revision of the template you have. The tags are the reliable signal there.
One more dependency is worth flagging because the README's feature list does not mention it: `babel-plugin-react-compiler` at 1.0.0. React Compiler support is configured but not advertised as a feature, so if you are looking for an explanation of why your build has a Babel plugin in it, the manifest is where the answer is.
A deliberately short tree with the configuration left visible
The root of the repository has twenty entries and every one of them earns its place. `.commitlintrc.json`, `.editorconfig`, `.oxfmtrc.json`, `.oxlintrc.json`, `.npmrc` and `.nvmrc` are the tool configuration, all plain files you can open and read. `next.config.ts` holds the Content Security Policy. `redirects.ts` holds the typed redirect array. `renovate.json` holds the dependency update rules. `tsconfig.json` holds the path mapping.
Then there is `pnpm-workspace.yaml`, which is worth pausing on because a single-package starter does not need one. Its presence suggests the repository is prepared for a monorepo layout, and it is also what makes the post-merge hook and the lockfile story consistent.
What is missing is the interesting part. There is no state management library, no component library, no data fetching layer, no test framework and no deployment configuration. That absence is the design. The description calls it a non-opinionated starter, and the tree is the evidence: every entry is either something the framework needs or a default you would otherwise write in the first hour of a new project.
The showcase list in the README gives a sense of scale, with sites including hygraph.com, rocketseat.com.br, a Notion avatar maker, an IKEA price tracker and freeinvoice.dev. Those are production deployments rather than demos, and the fact that a company site and a side project are both listed suggests the template ages well across very different levels of effort. The final entry on that list is an invitation to add your own project by editing the README, which is a small detail that tells you the maintainer still treats this as an ongoing contribution rather than a finished artifact.
Editorial conclusion
What this starter actually offers is a set of decisions already made and a way to argue with them. The defaults cover the things a new Next.js project ends up needing anyway: typed environment variables with a schema split between client and server, a typed redirect array, a Content Security Policy in the config file rather than in middleware, and hooks that keep commit messages conventional. The linting choice is the part worth thinking about, because Oxlint and Oxfmt are young enough that adopting them means accepting a toolchain the project itself only adopted in version 2.5.0, after version 2.4.0 had spent effort refining an ESLint configuration. The tree is short enough to read in full, which is the strongest argument for it: twenty entries, no framework wrapper, no state library, nothing hiding behind an abstraction. Create the project from the example command rather than copying a fork, since the repository is a template and the fork will not receive the Renovate bumps that keep the Next.js version current.
Frequently asked questions
How do I create a new project from this starter?
Use Create Next App with the example flag pointed at the repository URL: `pnpm create next-app -e https://github.com/jpedroschmitz/typescript-nextjs-starter`. Yarn and npm equivalents are in the README. This generates a fresh project rather than a clone, which is what you want if you intend to maintain it, and it is how you pick up later Renovate bumps of the template's dependencies.
Which linter and formatter does the starter use?
Oxlint and Oxfmt, in place of ESLint and Prettier. Version 2.5.0 migrated to them, and linting runs type-aware through the oxlint-tsgolint dev dependency. Configuration sits in `.oxlintrc.json` and `.oxfmtrc.json`, with scripts named lint, lint:fix, format, format:check and format:ci.
What Node.js and pnpm versions does it need?
The README documents Node.js 24 or newer and pnpm 10, but the package manifest declares `packageManager` as [email protected], so the manifest is the stricter of the two. If your package manager honours the packageManager field, expect it to install pnpm 12.3.2 rather than whatever you have. The `.nvmrc` file is the other place the Node expectation is recorded.
How do I add environment variables?
Create a `.env.local` file in the project root and add the variable to the schema in `./src/lib/env/client.ts` or `./src/lib/env/server.ts` depending on whether it reaches the browser. Validation is handled by T3 Env on top of Zod, so a missing or malformed value fails at the boundary rather than somewhere later in the request.
Can I switch from pnpm to npm or Yarn?
Yes. Delete pnpm-lock.yaml, install with the package manager you want, then update the CI workflow and the Husky hooks to match. If you use Yarn, follow the Husky documentation's guidance for Windows so the git hooks do not fail there. The post-merge hook that reinstalls on a lockfile change is pnpm-specific and needs attention too.
Why is the pre-commit hook disabled by default?
By choice, according to the README: the pre-commit hook runs lint-staged, which lints and formats staged files, and the maintainer judged that most developers do not want that on every commit. The commit-msg and post-merge hooks are enabled, so conventional commits and dependency syncing still happen. Enabling it is a local change to the Husky configuration.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/jpedroschmitz-typescript-nextjs-starter)