WallpaperMachine
당신이 사랑하는 배경화면, 맥에서 새롭게 만나보세요
몬트리올·토론토·오타와·미시소거·런던·키치너워터루 등에서 임대 아파트를 무료로 조회·예약
Apartments for rent across Montreal, Toronto, Ottawa, Mississauga, London and Kitchener-Waterloo. Browse live availability and book a visit — no cost to renters.
라이프스타일JavaScript · CSSGitHub @rb-qc-web온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
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.