Speed and file uploads
What this chapter is for. The first two features of this site are how fast a page arrives and whether a photo actually lands. Both were worked on together, because the same evening that the pages got a cache was the evening uploads started reporting a real reason instead of "Image upload failed".
Where the data lives, and in what order
memory (this tab, minutes) โ your browser's storage (hours to days) โ our API
Cities, areas, cuisines, categories, restaurant lists and a restaurant page are read from the first place they exist. The screen paints from that, refreshes quietly in the background, and writes the new answer back. Nothing is ever only in the cache: a stale picture is corrected in flight, and prices, tables and offers are always recalculated by the API.
What a restaurant sees. Opening the app again the next day brings yesterday's city and locality lists instantly. A customer who visited last week sees the restaurants they had loaded, names and photos, before any request finishes.
The storage key namespace is mcd-cache:v1: โ bumping that prefix retires every entry at once after a breaking API change, rather than leaving half the network reading old shapes.
The first screen is rendered on the server
The home page used to ship an empty shell and fill itself in the browser. A crawler, an AI tool or a link preview therefore saw a page with no restaurants on it, and a person saw a beat of nothing.
app/page.tsx now asks the API for the same sixteen-row list the client would have asked for and renders it into the HTML; the client takes over that data and refetches immediately. The fetch gets eight seconds rather than two, because a pooled database in another region legitimately needs a few seconds for a list, and the reader is served the previous snapshot while this render runs. If the API is slow or down, the page renders without the rail and the browser retries as before โ a slow database must never turn the home page into a long wait or an error. There is deliberately no spinner on that page: a loader is the one thing both a crawler and a waiting customer lose patience with.
Uploading a photo or a spreadsheet
Two numbers, the same on every screen and in the API:
| File | Limit |
|---|---|
| Logo, cover, gallery, menu photo | 8 MB |
| Menu spreadsheet | 5 MB |
Those limits are checked where you pick the file โ so an oversized photo is named, with its actual size, before it joins an upload queue โ and again by the API before the file is read. A spreadsheet must be .xlsx, .xls or .csv; an image must be JPG, PNG or WebP.
When the CDN is not configured
What happens. Photos go to the API server's own disk and are served from /uploads/โฆ through this site. If the CDN was configured and the upload to it failed, the reason is now written next to the error you see: "Image upload failed โ
Why it works that way. A half-set-up CDN used to swallow the image and report a bare failure โ the file was gone and the reason stayed in a server log. And in any deployment without ImageKit keys (this sandbox, a staging box, a new partner instance), uploading was simply impossible: the code returned null on success and the page then saved undefined as the URL. Storing the bytes next to the API is the honest fallback; the CDN is a performance choice, not a requirement.
What a restaurant sees. A cover photo that used to error now appears. Nothing you did wrong caused the old behaviour, and no configuration is needed to keep uploads working โ adding the three IMAGEKIT_* values is what moves new uploads onto the CDN.
What to check first. (1) The size and type of the file, against the table above. (2) UPLOAD_DIR โ the folder the API writes into; it must be on a disk that persists, or an image saved locally disappears when the container is replaced. (3) If a URL says /uploads/โฆ but the picture is missing, the API is not serving that folder โ check that UPLOAD_DIR points where the files actually are.
Numbers
| Fact | Value |
|---|---|
| Image and cover upload limit | 8 MB |
| Spreadsheet limit | 5 MB |
| Ceiling on the home page's server-side fetch | 8 seconds |
| Typical round trip to the pooled Postgres behind it | ~1 second |
| How often that pre-rendered home list is rebuilt | 60 seconds |
| How long a locally stored upload is cached by a browser | 7 days |
Ready to grow your restaurant?
Join 50+ restaurants on MyCityDeal and start receiving bookings today.