You've built a Next.js prototype and it needs to remember things: tasks, sign-ups, feedback, whatever the app is about. You don't want to provision a database, write migrations or run a server just to find out whether the idea works.
You have four realistic options. The right one depends on who needs to see the data.
1. localStorage: only you, only this browser
localStorage.setItem('tasks', JSON.stringify(tasks))
const saved = JSON.parse(localStorage.getItem('tasks') || '[]')
Zero setup, and fine for a demo on your own laptop. But the data lives in one browser. Open the prototype on your phone, send it to a colleague, or clear site data, and it's gone. It also can't be read by anything else, like a script or another service.
Use it when: the prototype is single-user and single-device.
2. A JSON file in the repo: read-only in production
Importing a data.json file works for seed data, but you can't write to it once deployed. Serverless platforms like Vercel have a read-only filesystem at runtime, so writes either fail or disappear on the next deploy.
Use it when: the data is fixed content, not user input.
3. A hosted database (Supabase, Firebase, Neon, and others)
These give you a real database with generous free tiers. They are the right choice once the prototype is becoming a product. The cost is setup: an account, a project, a schema or rules, an SDK, and auth decisions, before you've saved your first record.
Use it when: you're past the "does this idea work?" stage.
4. A hosted REST API with persistence
Somewhere in between: a hosted API you can POST JSON to and GET it back, with no schema to design. ReqRes works this way. You create a project (no account needed for an agent sandbox, or a free account to keep it), get a collection endpoint, and start saving records.
# Create a project and get keys (or sign up free at app.reqres.in)
curl -X POST https://reqres.in/api/projects/sandbox
You get two keys:
- A public key (
pub_...). It's read-only, so it's safe in the browser for loading data. - A manage key (
pro_...). It can write, so keep it on the server.
In Next.js, that maps neatly onto the App Router. Read in the browser, write through a Route Handler:
// app/api/tasks/route.js (runs on the server; the manage key never reaches the browser)
export async function POST(request) {
const task = await request.json()
const res = await fetch('https://reqres.in/api/collections/tasks/records', {
method: 'POST',
headers: { 'x-api-key': process.env.REQRES_MANAGE_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ data: task }),
})
return Response.json(await res.json(), { status: res.status })
}
// In a client component: read with the public key, write via your route
const res = await fetch('https://reqres.in/api/collections/tasks/records', {
headers: { 'x-api-key': process.env.NEXT_PUBLIC_REQRES_PUBLIC_KEY },
})
const { data } = await res.json()
await fetch('/api/tasks', { method: 'POST', body: JSON.stringify({ title: 'Write tests', done: false }) })
Two details that trip people up: record bodies are wrapped as {"data": {...}}, and a public key gets a 403 if you try to write with it. That's deliberate.
The free plan covers 250 requests a day, 3 collections and 100 records per project, which is plenty for a prototype. Paid plans raise the limits.
Use it when: other people need to see the same data, but you don't want to design a database yet.
Which should you pick?
| Shared across devices | Setup | Good for | |
|---|---|---|---|
| localStorage | No | None | Solo demos |
| JSON file | Read-only | None | Fixed content |
| Hosted database | Yes | An hour or more | Becoming a product |
| Hosted REST API | Yes | Minutes | Prototypes other people use |
If you're using an AI coding assistant, it can do option 4 for you: connect the ReqRes MCP server and ask it to "store the data in ReqRes". It creates the project and collections itself and gives you a link to keep them.