Build a data-intensive Next.js app with Tinybird and Cursor
Building a Next.js app is easy. Setting up infra to handle millions of logs isn't. Here's how I built a data-intensive Next app with Tinybird and Cursor.
![Build a data-intensive Next.js app with Tinybird and Cursor](/content/images/size/w2000/2025/02/Tinybird-x-Cursor---Njs---BlogPost-HeroImg.png)
Building a Next.js app is easy these days. With generative AI webdev platforms like v0, Lovable, and Bolt and AI-assisted IDEs like Cursor and Windsurf, you can pretty much spin up any Next.js app you can imagine in a few minutes, a day tops.
The challenge with webdev, of course, isn't webdev. It's getting the backend right, and making sure you have infrastructure set up for auth, data persistence, caching, logs, etc.
We're already seeing advances in AI there. Lovable and Bolt just added Supabase integrations, making auth and user data storage pretty simple even when prompting your way to your next great SaaS. And of course you can use Vercel to manage pretty much everything you need for deployments, serverless functions, etc.
But still, if you're building a data-intensive app - one that most process lots and lots of data such as logs or clickstreams - the infra problem can feel a bit tricky.
Tinybird is a great tool here - it gives you infra and tooling to build an analytics backend for your web app, and the integration process is very straightforward.
Here, I'll walk you through the process of building locally and deploying a data-intensive Next.js application using Tinybird and Cursor. Combining these two tools will make it very fast and easy to build and test a scalable and performant leaderboard app capable of processing millions or even billions of game logs.
The challenge with logs
Developing a Next.js app that handles large datasets can be challenging. The standard infrastructure that you might use for webdev often won't cut it. If you're storing millions or billions of logs, you'll quickly outgrow Postgres or any other relational database. So you'll be looking for a new home for all that data, which means you'll have to fuss with all the typical stuff of working with a database: clusters, ORMs, caching, RBAC, etc.
Tinybird simplifies this by providing infra and tooling to capture and store massive amounts of data, then query it and explore your queries as secure, scalable, OpenAPI spec REST Endpoints. In effect, it becomes an analytics backend for your app, handling all the things you'd otherwise have to build.
On to the build...
Building the Next app
To kick things off, I just started with an empty Next.js app and prompted Cursor to build something. Here's the prompt I gave to Cursor composer:
Cursor quickly built a simple leaderboard with time range filters, as I asked. It put everything in page.tsx
. Normally, I'd have created React components for the filter bar and the leaderboard table, but when you're asking LLMs to do the work, you can't complain.
Here's the leaderboard it created:
Not bad. Simple, but it's what I asked for. Cursor even generated mock data and created little avatars for the usernames. Nice touch.
Cursor also added the use client;
directive, which is fine for this little app because it's only handling 10 little bits of JSON for mock data, and the browser can easily handle all that.
But if we had millions or billions of game logs, this obviously wouldn't do. We can't load GBs of data into the browser and expect it to work.
So, we need to point this leaderboard at an API that can fetch the latest leaderboard based on the stored game logs.
For that, we use Tinybird.
Build a local Tinybird data project
Tinybird has great tooling for building data projects locally alongside your Next app. First, you install the CLI and authenticate (using Tinybird's tb commands):
When you login, you create a cloud workspace, which is where you'll eventually deploy the data project after you're done building locally.
For local dev, however, you just start the local Tinybird dev server:
This is a Docker image, so you just need a Docker runtime on your machine (if you're using MacOS, I recommend OrbStack).
From there, you can actually create all of the resources with just a prompt:
Tinybird takes that prompt and does the following:
- Creates the project scaffolding with folders for data sources, endpoints, tests, fixtures, and other things I may build later on
- Builds
.datasource
and.pipe
files to match the prompt - Creates 10 rows of mock data in an NDJSON fixture to match the generated data source
- Creates a
.cursorrules
file so Cursor has instructions for interacting with Tinybird resources - Generates YAML files for CI workflows on GitHub and GitLab
Here's the .datasource
file that Tinybird created to store my game logs, which lines up with what I asked for:
The schema defines JSON paths to columns. I can use the Tinybird Events API to stream JSON payloads to this endpoint, and it will write my logs into the underlying database table.
You'll notice that it wisely chose timestamp
as the primary sorting key, since Tinybird uses a columnar database, and that's what I'll primarily be filtering by.
And here's the endpoint pipe it created to query the game logs and build a leaderboard:
Just as I asked. A first node that filters by passed time range parameters (using Tinybird templating language for handling query parameters and logic), and a second node that calculates the leaderboard from the filtered data.
Building the data project
One thing I really like about Tinybird here is the tb dev
command. You're familiar with next dev
or npm run dev
, and tb dev
is basically the same thing, but for data. It watches my code and automatically rebuilds and validates my endpoints as I make changes.
I can't overemphasize how nice this is. Imagine if I were using a standard database for this with an ORM and some backend code. I'd have to run my Next.js app locally, and then point it at a remote API. If the API fails, I have to dig into the network logs, then go check out my backend service logs, then probably dive into the database logs. Such a nightmare just to do a quick local build. With this approach, I can literally see my changes (and any errors they introduce) immediately.
So I ran tb dev
, and it validated that I had a functioning API endpoint at http://localhost:80/v0/pipes/leaderboard_api.json
.
All that's left was to update my Next app to point to this new API.
Updating the Next app to point at the Tinybird API
Once I validated that I had a functional endpoint, I prompted Cursor to update the Next app to fetch the leaderboard from my API. Here's the prompt:
Cursor updated the leaderboard to fetch the API:
Cursor added this function to fetch data from the endpoint, with a useEffect
hook to refresh whenever I select a new time range in the app.
Initially, my leaderboard looked like this:
The API failed to fetch because Cursor didn't have access to a read token for my endpoint. Yay security!
Adding env variables
Tinybird creates tokens to give access to resources, like data sources and pipes. (You can create static tokens with row-level security policies or generate scoped JWTs).
During local dev, Tinybird automatically creates a local admin token for testing. I prompted Cursor to use environment variables for Tinybird's host and token when fetching the API, so it created .env.local
and .env.example
files in my app.
I updated my local environment variables with the following:
After that, the API started fetching from my Tinybird API, using the 10 rows of mock data it had generated during the initial prompted creation:
Of course, 10 rows isn't "data-intensive", so I asked Tinybird to mock 1,000,000 rows of data, using a prompt to define how I wanted it to generate timestamps so I could test my filters:
Tinybird created a new game_logs
fixture with 1M rows. Refreshing my Next app, the leaderboard now looks like this:
Every time I select a time range, the Next app fetches the latest filtered leaderboard from the API that I built.
I can easily filter by time range, and every time I do it's fetching the latest data. I'm not even using a cache, I'm going straight to the Tinybird database. The roundtrip for a cold start takes 500 ms without any optimization (I've only given my Docker runtime 2GB of memory to work with). Hot queries are responding in <100 ms.
That's the basic local build! I have a functioning Next.js app that is performantly fetching a leaderboard on 1,000,000 rows of logs without breaking a sweat.
Let's deploy this thing.
Deploy to the cloud
As I mentioned before, Tinybird automatically generates YAML templates for CI workflows. If I wanted to deploy Tinybird in CI/CD, I could simply update those files and deploy my resources to the cloud with a PR.
In this case, for my one off app, I'll just deploy my resources with the CLI:
This creates a deployment, waits for it to finish, and automatically promotes it to my live production deployment.
Navigating to cloud.tinybird.co, I find my deployment and my resources, plus some nice observability for their usage (these charts are empty, because I haven't hit the endpoint yet).
All that's left is to push my code to my git remote, link that repo to Vercel, and build the Vercel deployment:
I copied my .env
into the Vercel environment, updated the Tinybird host to point at my cloud host (https://api.europe-west2.gcp.tinybird.co
) and created a new static token with a limited scope to read the leaderboard API:
I pasted this token into my Vercel environment variables before deployment.
With that, I had a live Next app that was easily processing millions of rows of game logs and maintaining great performance. 🚀
Wrap up and resources
This is a simple example, my goal being to demonstrate the local build and deploy when using Tinybird alongside a Next.js app. There are a lot of complexities I didn't cover, such as creating an API gateway/proxy using Next.js middleware, properly caching endpoint responses, auth using JWTs, and a whole bunch of other stuff you'd probably do in a production environment.
Still, the point is that you can very easily deploy a data-intensive Next.js app, no matter how basic, using Tinybird as your analytics backend.
Now go build your own app! You can try a Tinybird template, or just start with a prompt. Here are some more resources to get you going:
And here's the video companion I created for the local build: