TanStack Start on Cloudflare: Part 1 - Linting and formatting
This is the second article in the series that explores how to build full-stack applications using TanStack Start hosted on Cloudflare.
Published: Updated:Articles in the series:
- Modern full-stack projects with TanStack and Cloudflare
- TanStack Start on Cloudflare: Part 1 - Linting and formatting (this article)
Where we left off
In the previous article we bootstrapped a very basic TanStack Start project using a starter provided by Cloudflare (see GitHub repository). The goal of this article is to make a step forward and set up formatting and liniting for the project. We will also remove the boilerplate code generated by the starter CLI to have a clean base for our application.
So far the structure of the project looks as follows:
$ tree --gitignore
.
├── LICENSE
├── README.md
├── package.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── public
│ ├── favicon.ico
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ ├── robots.txt
│ ├── tanstack-circle-logo.png
│ └── tanstack-word-logo-white.svg
├── src
│ ├── components
│ │ └── Header.tsx
│ ├── data
│ │ └── demo.punk-songs.ts
│ ├── logo.svg
│ ├── routeTree.gen.ts
│ ├── router.tsx
│ ├── routes
│ │ ├── __root.tsx
│ │ ├── demo
│ │ │ ├── api.names.ts
│ │ │ ├── start.api-request.tsx
│ │ │ ├── start.server-funcs.tsx
│ │ │ ├── start.ssr.data-only.tsx
│ │ │ ├── start.ssr.full-ssr.tsx
│ │ │ ├── start.ssr.index.tsx
│ │ │ └── start.ssr.spa-mode.tsx
│ │ └── index.tsx
│ └── styles.css
├── tsconfig.json
├── vite.config.ts
├── worker-configuration.d.ts
└── wrangler.jsonc
Let’s get started!
Installing new dependencies
There are many tools in the ecosystem that fulfill the tasks of linting and formatting. The most mature combo consists of ESLint and Prettier. This is what we are going to use in our project. If you are curious to explore alternatives, you may want to have a look at Biome or keep on eye on the suite of tools called Oxc. Both of these projects are written in Rust and promise unprecedented speed, compared to ESLint and Prettier. However, these tools are less mature and have less configuration options. Until they become more established, I stick with the battle-tested tools. Speed is not an issue for my setup because I prefer to run both the linter and the formatter on-save, rather than doing it separately on the entire codebase, every time I make a change. When the scope spans just a few modified files, these tools work fast enough not to bother about it.
So, let us add new dependencies to our codebase:
pnpm add -D eslint @tanstack/eslint-config prettier
Configuration
Both of these tools require a configuration file. Luckily, it is straightforward to set them up.
ESLint
The configuration package installed in the previous step contains all the necessary
setting required to set up linting for the TanStack Start project. So, we will just have
to create a new file called eslint.config.js in the root of the project, near the Vite
configuration file, with the content:
// @ts-check
import { tanstackConfig } from '@tanstack/eslint-config'
export default [...tanstackConfig]
Since we have enabled TypeScript checks for the config file, it has to be included in
the tsconfig.json file, as follows:
- "include": ["**/*.ts", "**/*.tsx"],
+ "include": ["**/*.ts", "**/*.tsx", "eslint.config.js", "vite.config.ts"],
Note that I also included the vite.config.ts file to enable similar checks there.
Prettier
Next up, we are going to set up the formatter. In a similar way, we have to create a
file called prettier.config.js in the root of the project with the content (feel free
to adjust the formatter options to your taste):
// @ts-check
/** @type {import('prettier').Config} */
const config = {
semi: false,
singleQuote: true,
trailingComma: 'all',
};
export default config;
Since prettier can format multiple file types that exist in our project, we want to add
an ignore-file to skip formatting the file that have been automatically generated by the
tooling we use, like pnpm-lock.yaml, for example. Add another file at the root of the
project called .prettierignore with the content (you may choose to add only the lock
file managed by your package manager):
package-lock.json
pnpm-lock.yaml
yarn.lock
.cta.json
worker-configuration.d.ts
Finally, as for ESLint, let us include the config file into tsconfig.json:
- "include": ["**/*.ts", "**/*.tsx", "eslint.config.js", "vite.config.ts"],
+ "include": [
+ "**/*.ts",
+ "**/*.tsx",
+ "eslint.config.js",
+ "prettier.config.js",
+ "vite.config.ts"
+ ],
NPM scripts
It is also useful to add a few NPM scripts to be able to invoke these tools on demand at
any time. For this, open up package.json file and modify it as follows:
"build": "vite build",
"serve": "vite preview",
"test": "vitest run",
+ "lint": "eslint",
+ "format": "prettier",
+ "check": "prettier --write . && eslint --fix",
"deploy": "pnpm run build && wrangler deploy",
"preview": "pnpm run build && vite preview",
"cf-typegen": "wrangler types"
Finally, to have a clean baseline that is compliant with the new rules, run the check
command:
pnpm run check
Removing boilerplate code
Now, when we have both the linter and the formatter installed and configured, let us get rid of the boilerplate code generated by the TanStack Start CLI when we created the project.
To clean up the project remove src/components, src/data, and src/routes/demo
folders, as well as logo.svg file. Next, update src/routes/__root.tsx file:
@@ -2,8 +2,6 @@ import { HeadContent, Scripts, createRootRoute } from '@tanstack/react-router'
import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'
import { TanStackDevtools } from '@tanstack/react-devtools'
-import Header from '../components/Header'
-
import appCss from '../styles.css?url'
export const Route = createRootRoute({
@@ -38,7 +36,6 @@ function RootDocument({ children }: { children: React.ReactNode }) {
<HeadContent />
</head>
<body>
- <Header />
{children}
<TanStackDevtools
config={{
Finally, update the content of the src/routes/index.tsx file as follows:
@@ -1,118 +1,7 @@
import { createFileRoute } from '@tanstack/react-router'
-import {
- Route as RouteIcon,
- Server,
- Shield,
- Sparkles,
- Waves,
- Zap,
-} from 'lucide-react'
export const Route = createFileRoute('/')({ component: App })
function App() {
- const features = [
- {
- icon: <Zap className="w-12 h-12 text-cyan-400" />,
- title: 'Powerful Server Functions',
- description:
- 'Write server-side code that seamlessly integrates with your client components. Type-safe, secure, and simple.',
- },
- {
- icon: <Server className="w-12 h-12 text-cyan-400" />,
- title: 'Flexible Server Side Rendering',
- description:
- 'Full-document SSR, streaming, and progressive enhancement out of the box. Control exactly what renders where.',
- },
- {
- icon: <RouteIcon className="w-12 h-12 text-cyan-400" />,
- title: 'API Routes',
- description:
- 'Build type-safe API endpoints alongside your application. No separate backend needed.',
- },
- {
- icon: <Shield className="w-12 h-12 text-cyan-400" />,
- title: 'Strongly Typed Everything',
- description:
- 'End-to-end type safety from server to client. Catch errors before they reach production.',
- },
- {
- icon: <Waves className="w-12 h-12 text-cyan-400" />,
- title: 'Full Streaming Support',
- description:
- 'Stream data from server to client progressively. Perfect for AI applications and real-time updates.',
- },
- {
- icon: <Sparkles className="w-12 h-12 text-cyan-400" />,
- title: 'Next Generation Ready',
- description:
- 'Built from the ground up for modern web applications. Deploy anywhere JavaScript runs.',
- },
- ]
-
- return (
- <div className="min-h-screen bg-gradient-to-b from-slate-900 via-slate-800 to-slate-900">
- <section className="relative py-20 px-6 text-center overflow-hidden">
- <div className="absolute inset-0 bg-gradient-to-r from-cyan-500/10 via-blue-500/10 to-purple-500/10"></div>
- <div className="relative max-w-5xl mx-auto">
- <div className="flex items-center justify-center gap-6 mb-6">
- <img
- src="/tanstack-circle-logo.png"
- alt="TanStack Logo"
- className="w-24 h-24 md:w-32 md:h-32"
- />
- <h1 className="text-6xl md:text-7xl font-black text-white [letter-spacing:-0.08em]">
- <span className="text-gray-300">TANSTACK</span>{' '}
- <span className="bg-gradient-to-r from-cyan-400 to-blue-400 bg-clip-text text-transparent">
- START
- </span>
- </h1>
- </div>
- <p className="text-2xl md:text-3xl text-gray-300 mb-4 font-light">
- The framework for next generation AI applications
- </p>
- <p className="text-lg text-gray-400 max-w-3xl mx-auto mb-8">
- Full-stack framework powered by TanStack Router for React and Solid.
- Build modern applications with server functions, streaming, and type
- safety.
- </p>
- <div className="flex flex-col items-center gap-4">
- <a
- href="https://tanstack.com/start"
- target="_blank"
- rel="noopener noreferrer"
- className="px-8 py-3 bg-cyan-500 hover:bg-cyan-600 text-white font-semibold rounded-lg transition-colors shadow-lg shadow-cyan-500/50"
- >
- Documentation
- </a>
- <p className="text-gray-400 text-sm mt-2">
- Begin your TanStack Start journey by editing{' '}
- <code className="px-2 py-1 bg-slate-700 rounded text-cyan-400">
- /src/routes/index.tsx
- </code>
- </p>
- </div>
- </div>
- </section>
-
- <section className="py-16 px-6 max-w-7xl mx-auto">
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
- {features.map((feature, index) => (
- <div
- key={index}
- className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-xl p-6 hover:border-cyan-500/50 transition-all duration-300 hover:shadow-lg hover:shadow-cyan-500/10"
- >
- <div className="mb-4">{feature.icon}</div>
- <h3 className="text-xl font-semibold text-white mb-3">
- {feature.title}
- </h3>
- <p className="text-gray-400 leading-relaxed">
- {feature.description}
- </p>
- </div>
- ))}
- </div>
- </section>
- </div>
- )
+ return null
}
Outro
What we managed to achieve so far is to bootstrap a TanStack Start + Cloudflare project, set up linting and formatting, and clean it up to prepare for adding any custom features. In the upcoming articles we will continue building on this setup to add more functionality. Eventually, we will build an example application that demonstrates the power and flexibility of this setup. Stay tuned!