Skip to the main content
onedollarpixel
The gridAdvertisersAboutRulesFAQ
Documentation / Quickstart

npm run quickstart

One command takes a downloaded copy of this template to a live board on your own domain, with the Stripe webhook already registered.

npm ci
npm run quickstart

Budget about fifteen minutes. Most of that is Vercel building your board twice.

npm ci is listed first because it is the ordinary thing to do, and because it is the fastest path. You may skip it: the setup checks for your dependencies before it creates anything, tells you what it found, and runs npm ci itself when it needs to. That adds one to three minutes, once.

Read the next section before you run it. This command creates real resources inside accounts that belong to you, and you should know which ones before it starts rather than after.


1. What it creates inside your own accounts

WhereWhat
NeonA project, named after your board. This is your database.
NeonA branch on that project called test. The automated tests use it and nothing else.
NeonA database called outbid_test inside that branch. This is what the tests destroy.
VercelA project, linked to this directory. This is where your board runs.
StripeA webhook endpoint at https://your-domain/api/webhooks/stripe, subscribed to five events.

All five are free at the tier this creates. They are still resources in accounts you pay for, so they are listed here rather than mentioned afterwards. Section 3 says how to undo each one.

Two limits are worth knowing before you start:

  • Neon's free plan limits how many projects one account may have. If you are at that limit, creating the project is refused. The command prints the exact command that failed and what Neon said; remove a project you no longer need, or use a paid plan, and run npm run quickstart again.
  • On Vercel's Hobby plan the deploy is REFUSED, not slowed down. vercel.json schedules /api/cron every five minutes, a Hobby account may only run a cron once a day, and Vercel declines the whole deployment rather than downgrading the schedule for you. Measured on a real Hobby account, 2026-08-23:
  Error: Hobby accounts are limited to daily cron jobs. This cron expression
  (*/5 * * * *) would run more than once per day. Upgrade to the Pro plan to
  unlock all Cron Jobs features on Vercel.

This does not end your run. The command recognises that message, explains what the cron does and what a daily schedule costs, and offers three ways forward: a daily cron (it changes one line in vercel.json and deploys), no cron in vercel.json (it removes the entry, deploys, and prints one curl line you schedule yourself, keeping the five-minute cadence), or an upgrade to Pro (it stops, and the next run resumes from exactly that point). Nothing in vercel.json is rewritten until you answer, and your answer is remembered so a resumed run does not ask again.

It also says the caveat before it sets anything, whichever plan you are on: no vercel command reports the plan, so the note ahead of the deploy is a caveat to read rather than a finding about your account. The refusal itself is the one thing that states the plan, and the command records it when it fires.

How your values are stored on Vercel

Every variable the command sets is stored as sensitive, which is Vercel's default and means the value cannot be read back out of the dashboard afterwards. Your database URL, your Stripe keys, the operator password hash, the operator session secret and the cron secret are all in that group. Your own copy of each is in .env.local.

NEXT_PUBLIC_SITE_URL is the one exception, and it is not a choice. Next compiles a NEXT_PUBLIC_* value into the JavaScript every visitor downloads, so it is public by definition and Vercel refuses to store it as a secret. The command stores that one as configuration.

The test branch is not a nicety, and neither is the database in it

The money-path tests TRUNCATE every table before each case. That is the only honest way to test a payment path, and it destroys whatever database it is pointed at. A separate branch makes TEST_DATABASE_URL correct by construction instead of by warning, and it costs one command.

The branch alone is not enough. In Neon the branch is the isolation boundary, and the database inside a branch keeps the project's default name — neondb. The tests refuse a database whose name does not say test, because a name is the only signal that tells a throwaway apart from your board. So the setup also creates a database called outbid_test inside that branch, and points TEST_DATABASE_URL at it. Your board's own DATABASE_URL still ends /neondb and is never touched.

That database is REMOTE, so npm test is slow

TEST_DATABASE_URL points across the network at Neon. Every query in the money-path tests is a round trip to a server in another region, and those tests TRUNCATE every table before each case — so they are far slower than the rest of the suite, and how much slower depends on how far you are from your Neon region. The rest of the suite touches no database and stays fast.

This is the correct default. A buyer on Vercel and Neon has no local Postgres, and a setup that left TEST_DATABASE_URL empty would leave you with no way to run the money-path tests at all.

If you want them fast, point them at a Postgres on your own machine. Two things are required, and the second is the one people miss:

  1. Postgres, reachable from where you run the tests.
  2. A database whose NAME contains test. The tests refuse anything else, because a name is the only signal that tells a throwaway apart from your board. board_test, outbid_test and ci_test all qualify; postgres and board do not.
createdb outbid_test

Then put that one line in .env.local, replacing the Neon value:

TEST_DATABASE_URL=postgres://localhost:5432/outbid_test

and fill it in the same way the setup did:

npm run db:migrate:test

Your board's DATABASE_URL does not change, the Neon test branch stays where it is, and you can put the Neon value back at any time. Nothing in the board reads TEST_DATABASE_URL; only the tests do.


2. What it asks you for

Seven answers, in this order, and one more after your board is live. Nothing else is typed.

QuestionNotes
Board nameThe page title and the share card. 60 characters at most.
Domainboard.acme.dev. No domain yet? Type a bare name and you get <name>.vercel.app, free.
CurrencyLowercase ISO code. usd by default.
Minimum first bidA whole number in that currency. 2 by default.
Operator emailYour sign-in at /operator. There are no other accounts.
Operator passwordTyped twice, and never shown. At least 12 characters.
Stripe secret keyOne key, from <https://dashboard.stripe.com/apikeys>. Start with sk_test_.

Everything else is generated: the operator session secret, the cron secret, both database connection strings, and the Stripe webhook signing secret.

The password and the key are read without echo. Nothing appears as you type them, not even asterisks, so neither ends up in your terminal scrollback or in a screen recording of your setup.

The command never prints a secret. When it finishes it prints your board URL, your operator URL, and your operator email. Nothing else.

It writes .env.local in this directory. Values already in that file are kept. Any comments in it are not — if you have hand-written notes in there, copy them somewhere first.

It never starts a development server. It tells you what to run.

The eighth question: your own domain

It is asked last, after your board is already live, and that order is deliberate. You deploy to a free <project>.vercel.app address, open it, and only then decide about a domain. A DNS step that fails at that point costs you nothing — you already have a working board — and running the command again comes straight back to this question.

Three answers:

AnswerWhat happens
I already own a domainIt is attached to your project, the DNS record Vercel wants is printed, and the command waits up to five minutes for it to resolve.
Buy one for meThe name is checked, Vercel's price and its RENEWAL price are shown, and nothing is bought until you type the price back to the penny.
Not nowThe board keeps its vercel.app address. Run the command again whenever you want a domain.

Attaching a domain moves four things, and it moves all four or none. Your site URL is compiled into the board, so a domain attached without them would be a domain the board does not know it has:

  1. NEXT_PUBLIC_SITE_URL on the Vercel project.
  2. board.domain in outbid.config.ts — the canonical URL and the share image.
  3. The Stripe webhook endpoint URL. This is the one that matters. An endpoint still pointing at the old address takes the payment and never grants the rank, and nothing on the board looks broken while it happens.
  4. A redeploy, so the new address is compiled in.

The DNS wait gives up rather than blocking. A record added at a registrar can take an hour. When the wait runs out the board keeps its vercel.app address and the command says exactly what to create and that running it again finishes the job.

The purchase is the only thing this command does that costs money, and it is the only question here that a keypress cannot answer. You type the exact price. Anything else means no, and nothing is re-asked into submission.

It never issues a certificate by hand. vercel certs --help says Vercel manages certificates automatically, and issuing one on a first deploy asks for a certificate that already exists.

It never guesses a DNS record. vercel domains add prints the record it wants and vercel domains verify explains what is wrong with the one that is there. Both are repeated to you word for word. A guessed A record fails in the direction that looks like success: the record is created, the command exits 0, and the site is somebody else's.


3. How to undo each thing

In the order that matters, if you want everything gone.

The Stripe webhook endpoint

Open <https://dashboard.stripe.com/webhooks>, click the endpoint whose description says Created by npm run quickstart, and delete it.

The endpoint id is in .quickstart/state.json under ids.stripeWebhookEndpointId if you would rather do it from the command line:

curl -X DELETE https://api.stripe.com/v1/webhook_endpoints/we_YOUR_ENDPOINT_ID \
  -u sk_test_YOUR_KEY:

Deleting the endpoint stops delivery immediately. It does not refund anything and it does not touch your listings.

The Vercel project

Vercel dashboard → your project → Settings → Delete Project. Or:

npx vercel project rm your-project-name

This removes the deployment and the environment variables you set on it. It does not touch your database.

A domain you attached

Vercel dashboard → your project → Settings → Domains → remove it. Or:

npx vercel domains rm your-domain.com

Point the Stripe webhook back first. Removing the domain leaves the endpoint delivering to an address that no longer answers, which is the same silent failure as never having moved it: the payment is taken and the rank is never granted. Open <https://dashboard.stripe.com/webhooks>, open the endpoint, and set its URL back to https://<project>.vercel.app/api/webhooks/stripe.

A domain you BOUGHT is not removed by this. You still own it, it still renews, and it is in .quickstart/state.json under ids.domainBought. Turn the renewal off with npx vercel domains auto-renew your-domain.com off if you do not want it.

The Neon test branch and its outbid_test database

Neon console → your project → Branches → test → delete. Or:

npx neonctl branches delete test --project-id YOUR_PROJECT_ID

Deleting the branch removes the outbid_test database inside it. Only the tests use either one, so this does not affect your board.

The Neon project

Neon console → your project → Settings → Delete project. Or:

npx neonctl projects delete YOUR_PROJECT_ID

This destroys your board's data, including the test branch, every listing and every payment row. There is no undo.

The files on your own machine

.env.local holds your keys. .quickstart/state.json holds the record of what finished. Both are in .gitignore, and both are yours to remove.


4. When it stops

Run it again. Finished steps are skipped, and nothing is created twice.

Stop it whenever you like with Ctrl+C. It also stops on its own when something external refuses — a wrong flag, a plan limit, a network drop — and it always says which command failed and what that tool said.

Two files remember, and they remember different things.

.quickstart/state.json — the ledger

Which steps finished, when, and the ids of what they created:

{
  "version": 1,
  "steps": { "brand": "…", "operator": "…", "neon": "…" },
  "ids": { "boardName": "Acme Board", "neonProjectId": "…" }
}

It holds no secret, and that is enforced rather than intended. The writer refuses a value that looks like a credential — a sk_, rk_, whsec_ or pk_ prefix, a connection string, credentials inside any URL, or anything longer than 64 characters — and the refusal happens before the write, so a refused value never reaches the file.

.env.local — the values

Your database URLs, your keys, your operator hash. This is the file your board already reads, so a resume finds its values there rather than in a second copy somewhere else.

The one thing that cannot resume

Stripe returns a webhook signing secret only when the endpoint is created. A later read of the same endpoint never includes it. So if you lose STRIPE_WEBHOOK_SECRET after the endpoint exists, no command can recover it: open <https://dashboard.stripe.com/webhooks>, roll the secret on that endpoint, put the new value in .env.local and in your Vercel environment, and deploy again. The command names the existing endpoint when it hits this, so you never end up with two endpoints and one of them delivering to nothing.

Starting over — npm run quickstart -- --fresh

Forgets everything on this machine and asks the questions again from the first one. It never removes anything, here or in your accounts.

npm run quickstart -- --fresh      # asks before it touches anything
npm run quickstart -- --fresh --yes  # skips the question
npm run quickstart -- --help       # the flags, and nothing else

Before it changes a thing, it prints the whole plan and waits: the files that will move, the values that will be put back, and — in full, with ids — the Neon project, the Vercel project, the Stripe webhook endpoint and the deployed address that it is going to leave exactly where they are. Read it and stop if it is not what you meant.

What moves, all of it still readable afterwards under its new name:

FromTo
.quickstart/state.json.quickstart/state.<timestamp>.json
.env.local.env.local.<timestamp>

.env.local is the one that matters: your live Stripe key is in it, and you must be able to get it back.

What goes back to what the template ships, with a timestamped copy kept in .quickstart/ first:

FileBack to
outbid.config.tsYour Board, board.example.com, usd, 200
vercel.jsonthe */5 * * * * cron schedule

What it does not touch: .vercel/ — the link to your Vercel project; run npx vercel link again if you want this directory pointed at a different one — and every resource in your Neon, Vercel and Stripe accounts. Section 3 above says how to remove each of those yourself, and --fresh prints the same commands with your own ids filled in.

An unknown flag stops the command and prints the usage. --frsh is not --fresh, no prefix is accepted, and nothing is moved on the way to finding that out.


5. If a Neon or Vercel command is refused

Both command-line tools change. When one of them refuses a flag, you see the exact command that ran and the tool's own words:

Creating the Neon project failed.

  The command was:
    npx --yes neonctl projects create --name acme-board --output json

  neonctl said:
  unknown flag --output

Run that command yourself with the corrected flag, then run npm run quickstart again. It resumes from where it stopped.


6. Afterwards

Complete one test payment. Start here lists the seven checks that prove the board actually works, end to end.

Then read Cold start before you announce anything. An empty board is a dead board.


What this command does not cover

  • Any host other than Vercel, and any database other than Neon. The manual path in Start here covers DigitalOcean, Hostinger, a Docker host and your own server, and it is not going away.
  • Bidder email. It is off by default and needs a Resend account plus DNS records you have to add yourself. See Deployment (docs/deployment.md).
  • Going live. The command leaves you on a Stripe test key on purpose. Start here has the four steps to real money.

Payments

This template is Stripe-only. Stripe is the rail it ships, the rail its tests prove, and the rail npm run quickstart configures. With Stripe you are the seller, which means the VAT or sales tax is yours — /refunds says exactly that, so a bidder is never told the wrong thing about who they bought from.

The rail is still pluggable: src/payments/types.ts is a four-method interface and the webhook route resolves the provider from the URL. Payments walks the whole job and ends with the sandbox check to run before your own adapter ever sees a real card.

All documentationBack to the board

onedollarpixel · rank is the bid. · v2.13.1

AboutRulesFAQTermsDocs