The prototype works. You added a task, refreshed the page, and it was still there. Then a friend opened the same URL and saw an empty list. Nothing broke. The data was only ever on your laptop, in your browser, and no one else was ever going to see it.
The idea
Every piece of information in your app has a home, and the home decides who can see it and how long it survives. There are three realistic homes for a small project: a file that ships with your code, Local storageA small notepad the browser keeps for one website on one device. It survives a refresh and belongs to that one person. inside one visitor's browser, and a database on a server. Picking wrong is not fatal, but the cost of moving grows with every row you write.
WHO SEES IT -> WHERE IT LIVES -> GONE WHEN
------------------------------------------------------------------
everyone, a file in your repo you edit it and
identically content/*.mdx, .json redeploy
------------------------------------------------------------------
one person, localStorage they clear the
one browser ~5 MB, text only browser or switch
browser tab to their phone
------------------------------------------------------------------
one person, SQLite the server's disk
one server a single .db file is wiped
------------------------------------------------------------------
everyone, live, Postgres you delete it, and
many at once (Supabase, Neon, RDS) your backupsHow it works
A file in your repository is read at build time. It is perfect for things that change when you change them: product pages, blog posts, pricing copy, config. It is public, it is in git, and a visitor can never add to it.
localStorage is a key-value notepad the browser keeps per site, per device, capped around 5 MB, and it only holds text. Good for a theme preference, a dismissed banner, a draft the user has not submitted yet. Bad for anything you need on their other device, anything two people share, and anything you would be upset to lose. The user can open developer tools and edit it, so it is never a place to record what someone paid or what they are allowed to do.
A database is a separate program built to hold changing records for many people at once, answer questions about them quickly, and not lose them when your app restarts. SQLite is a database in a single file on one machine, with no server to run. Postgres runs as its own service and is what Supabase, Neon and most hosts give you. Both speak Structured Query LanguageThe language you use to ask a database questions and to change what it holds..
What to do
- Before you build a feature, say who owns the data and how long it must live. "Any visitor, until they delete it" means a database. "This one person, until they clear their browser" means localStorage.
- Default to Postgres for anything a user creates. Supabase gives you a hosted one in about two minutes, and starting there costs you nothing you would not have paid later.
- Keep secrets and personal information out of files in your repository and out of localStorage. Both are readable by people who should not read them.
Where it breaks
The advice inverts for content. Putting your marketing pages in Postgres so they are "editable" adds a database call, a schema and a backup story to something a markdown file did better. SQLite is a serious database, not a toy, and it is a good answer for a single-server app, but it is a bad answer the moment your host runs two copies of your app or throws away the disk between deploys. And a database is not safety by itself. It only protects data you have actually backed up and restricted access to, which is a decision, not a default.