WallpaperMachine
The wallpapers you love. A new home on your Mac.
Apartments for rent across Montreal, Toronto, Ottawa, Mississauga, London and Kitchener-Waterloo. Browse live availability and book a visit — no cost to renters.
LifestyleJavaScript · CSSGitHub @rb-qc-webOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗Next.js 14 site for RentBolt, deployed on Vercel. Listing data lives in monday.com; the site reads it, caches it in Redis, and renders it.
monday.com ──► lib/monday.js ──► Upstash Redis ──► pages
(5 boards) parse + shape 15 min cache
cron every 10 min
Nothing writes back to monday.com. Two things live outside monday:
| Data | Where | Why |
|---|---|---|
| Photo galleries | Redis rentbolt:gallery:<buildingId> | monday subitem boards don't reliably have the column we need (InvalidColumnIdException). Redis has no schema to fight. |
| Pricing | the inventory feed (see below) | monday subitem prices were missing on 63% of listings and go stale silently. |
| Saved-list leads | Redis rentbolt:lead:<id> + rentbolt:leads | Redis was already provisioned. Migrate to monday when the other lead forms are wired. |
Everything else — pricing, status, amenities, descriptions — is monday only. Edit there and it appears on the site within ~10 minutes.
monday.com generates a unique column id per board. lib/monday.js has one
COL map built from Montreal column ids and applies it to all five boards.
Any field in that map silently reads as empty on the other four — no error, no
warning, just missing data that surfaces weeks later as "the filter doesn't
work in Toronto".
This has caused at least four separate bugs: location columns, amenities /
inclusions / appliances, unitsAvail (which emptied the bed filters
everywhere but Montreal), and the promo column.
Before debugging anything that looks like missing data, run
/api/debug-columns. It lists, per board, every expected column that
doesn't exist there and suggests same-type replacements.
Two fields already work around this:
"2 Bed", "Studio")
as well as the dropdown, so they work on every board.| City | Board ID | Location column |
|---|---|---|
| Montreal | 3743206409 | location |
| Ottawa | 4955235841 | location |
| Toronto | 18402583974 | location_mm3yrv60 |
| London | 18401824343 | location_mm5eg13x |
| Kitchener-Waterloo | 5892819868 | location_mm3yr7wf |
| Mississauga | 18431630361 | location_mm3yrv60 |
| Ajax | 18430413833 | location_mm3yrv60 |
Only items with status Lease-Up or Stabilization are published.
Mississauga and Ajax were duplicated from the Toronto board, so they inherit
Toronto's column ids exactly — including the dropdowns in COL_EXTRA. That is
inheritance, not coincidence: a board rebuilt from scratch instead of duplicated
would get fresh ids and need its own entry.
Three Mississauga buildings sit on one street (1423 / 1447 / 1547 Mississauga
Valley Blvd), so all three publish as "Mississauga Valley Boulevard Apartments".
Known and accepted for now; the publicNameOverride hook below is the fix.
The board list is duplicated in /api/debug-boards and /api/debug-columns.
Adding a board means editing all three.
Building identity is stripped server-side in parseBuilding, before
anything reaches the browser. Masking at render time is not enough — the data
also sits in the JSON payload, the URL slug and the page metadata.
1445 Rue du Fort → Rue du Fort Apartments)lib/publicName.js honours a publicNameOverride field if a "Public Name"
column is ever added in monday.
| Variable | Used for |
|---|---|
MONDAY_API_KEY | reading the boards |
UPSTASH_REDIS_REST_URL / UPSTASH_REDIS_REST_TOKEN | cache, galleries, leads |
CLOUDFLARE_ACCOUNT_ID / CLOUDFLARE_API_TOKEN | photo uploads |
CLOUDFLARE_ACCOUNT_HASH | building delivery URLs (has a fallback) |
ADMIN_PASSWORD | /admin/photos, /admin/leads and all /api/admin/* |
MONDAY_LEADS_BOARD_ID | optional — mirrors each lead onto a monday board. Absent = leads live in Redis only |
CRON_SECRET | authenticating the scheduled refresh |
CACHE_REFRESH_SECRET | manual cache clear |
NEXT_PUBLIC_CARTO_API_KEY | optional — switches the map to Carto Positron. Absent = keyless Esri Light Gray |
RENTBOLT_PRICING_URL / RENTBOLT_PRICING_API_KEY | the pricing feed. Absent = the site falls back to monday subitem prices. No NEXT_PUBLIC_ prefix — these must never reach a browser |
RENTBOLT_PRICING_REFRESH_MINUTES | optional — pricing refresh interval. Default 1440 (daily) |
Env vars load at build time. After changing one in Vercel you must redeploy, or the running deployment won't see it.
/admin/photos, signs in with ADMIN_PASSWORDFiles go browser → Cloudflare directly via Direct Creator Upload. They do not pass through Vercel, which caps request bodies at 4.5MB while these photos run 2–5MB. The API token stays server-side; only a one-time upload URL reaches the browser.
Known upload failures, both pre-checked with a clear message:
type.startsWith("image/")
check but Cloudflare rejects itCoverage: /api/photo-status.
Four forms capture leads. All four post to one of two endpoints, and everything
lands in one Redis sorted set (rentbolt:leads), newest-first:
| Form | Kind | Endpoint |
|---|---|---|
/find-a-place, and the modal on the home/search/detail pages | tenant | /api/leads |
/landlords partnership form | landlord | /api/leads |
| "Book a visit" on a building page | visit | /api/leads |
/saved — the saved-homes list | saved-list | /api/save-list |
Read them at /admin/leads (same ADMIN_PASSWORD as the photo tool),
filterable by kind and exportable to CSV.
Three deliberate decisions:
MONDAY_LEADS_BOARD_ID and each lead is also pushed to that
board — best-effort, and a failure is logged rather than shown to the visitor.lib/submitLead.js, which reads the response as text before parsing —
a 500 from Next returns an HTML page, and res.json() on that throws a
SyntaxError that would otherwise be shown to the visitor as the reason their
form failed. If Redis is unconfigured the visitor is told to email us instead.
Every form has a visible error state; none of them fake a thank-you.Operational
| Route | Purpose |
|---|---|
/api/cron-refresh | scheduled rebuild, every 10 min |
/api/cache-refresh | clear the cache manually |
/api/photo-status | photo coverage per city, and who still needs photos |
/api/pricing-status | whether the pricing feed is on, and price coverage per city |
Diagnostics — these exist because each one was written to chase a real bug.
| Route | Answers |
|---|---|
/api/data-health | start here. Everything missing across every board, ranked by cost. ?names=1 lists the buildings, ?city=X narrows |
/api/debug-columns | which expected columns don't exist on which board |
/api/debug-boards | per-board health; ?heavy=1 uses the production query shape. Also names active listings missing coordinates |
/api/debug-beds?city=X | parsed bedrooms per building, and how many have no unit data |
/api/debug-neighbourhoods?city=X | borough coverage and counts |
/api/find-building?name=X&board=ID | full column + subitem dump for one building |
Admin (all require the x-admin-password header)
/api/admin/ping · /api/admin/buildings · /api/admin/upload-url ·
/api/admin/save-gallery · /api/admin/leads (?kind=tenant, ?format=csv)
Public POST
/api/leads (tenant / landlord / visit) · /api/save-list (saved homes)
A city disappeared from the site
Almost certainly a monday query failure. /api/cron-refresh returns
boardFailures. The site backfills that city from a last-known-good snapshot
rather than dropping it, so this should degrade to slightly stale data rather
than a missing city.
Data looks stale
Two caches, and they are independent. Redis (10 min) and the page render.
/search is force-dynamic; the homepage and listing pages are ISR at 600s.
A page cache once served hour-old HTML while Redis was perfectly current —
check both before concluding anything.
A listing doesn't appear
In order: status must be Lease-Up or Stabilization; it needs coordinates
(/api/debug-boards names the ones missing them); then check
/api/debug-columns for that board.
Bed filters return nothing for a city
/api/debug-beds?city=X. noBedroomData counts buildings with no unit
subitems — those are invisible to every bed filter. That's a monday data gap,
not a code bug.
Cache keys
CACHE_KEY must be bumped when the building shape changes, in three files:
lib/monday.js, app/api/cron-refresh/route.js, app/api/cache-refresh/route.js.
Currently rentbolt:buildings:v21.
LAST_GOOD_KEY sits alongside it and must be bumped too — it was missed by
every bump up to v20 because the string doesn't contain buildings:vNN.
Each of these cost real debugging time. Don't undo them.
FormData/Blob. The form-data npm
package produces a malformed multipart body on Vercel's runtime.mondayQuery must be cache: "no-store". It once had
next: { revalidate: 3600 }, creating a hidden Next.js cache underneath
Redis. /api/cache-refresh only cleared Redis, so stale monday responses
were laundered as fresh indefinitely.catch { return [] } is how
Montreal vanished silently for weeks.0,0 mean "no location", not Null Island. Anything that
transforms coordinates must leave zeros alone, or a pin lands off Africa and
drags the map viewport across the Atlantic.Prices come from an inventory feed, not monday. lib/pricing.js fetches all
buildings in one authenticated server-side request, once a day, and keeps
the last good snapshot in Redis so it survives serverless invocations and any
feed outage.
Daily rather than the contract's 15 minutes, to keep load off the inventory
platform. The cost is latency: a price that changes just after a fetch is not
reflected until the next one, so a rented unit's price can show for up to 24
hours. Hit /api/cache-refresh to pull immediately after reconfirming
inventory. RENTBOLT_PRICING_REFRESH_MINUTES overrides the interval without a
deploy if that proves too slow.
Rules that are deliberate, not incidental:
The feed's freshness window is 7 days and is enforced on the feed side, not here. The site publishes whatever it receives.
name + geometry, round coordinates to 5 decimals, save to
data/boroughs-<city>.jsonSETS in lib/neighbourhoods.jsNo code changes. A city without a file simply doesn't show the filter.
Currently loaded: Montreal (38 boroughs) and Toronto (140 neighbourhoods). Toronto's file covers the City of Toronto only — buildings on that board located in Mississauga or elsewhere in the GTA resolve to no neighbourhood, which is the same behaviour as having no file at all.
Still to source: Ottawa, London, Kitchener-Waterloo.