Introduction
For a rental property site, photos are everything. The trouble is that posting shots straight from a phone or camera means lining the site with 5MB files. Pages get slow, and guests browsing on mobile data pay the price.
But firing up image-editing software to resize every picture before writing a post isn't realistic either. This article covers how we built a pipeline where optimization finishes the moment you upload.
Inspecting the uploaded file
Never trust the extension
Start from the assumption that an uploaded file can't be trusted. A filename ending in ".jpg" tells you nothing about what's actually inside.
Reject anything over the limit, and anything empty
Reject anything outside the permitted image formats (JPEG, PNG, WebP, GIF, AVIF)
Parse it with an image-processing library and confirm it really is the image it claims to be
A file that says JPEG but contains something else gets stopped right here
Step three — actually reading the contents — is the important one. Every format starts with a distinctive marker (a signature), and we verify it with the image-processing library. Files whose name and contents disagree get thrown out at this point.
We don't permit vector images (SVG) at all. SVG files can contain scripts, which makes them a viable attack route dressed up as an image.
Don't reuse the filename
When we save a file, we don't use the name it arrived with. It gets a fresh name built from a random identifier.
Reusing the original name opens the door to overwriting an existing image with a same-named file, or to a crafted name that writes to somewhere it shouldn't. The original filename is only recorded in the database so it can be displayed in the dashboard.
Optimizing automatically
Resizing and format conversion
Images that pass inspection aren't stored as-is — they're processed first.
We configured it to leave images alone if they already fit, so small originals never get forcibly enlarged. Enlarging only makes things blurry — there's nothing to gain.
Stripping location data
Photos taken on a phone often carry the latitude and longitude of where they were shot. Publish an exterior photo as-is and the exact address can be read straight out of the image file.
Photos can give the location away
With short-term rentals, it's standard practice to share the exact address only with confirmed guests. Carefully withholding the address means nothing if the location data is still sitting in the photos.
The image-processing library we used doesn't carry metadata across during conversion, so location data is removed automatically as part of the WebP conversion. The one piece we handle first is the capture orientation, which we apply as an actual rotation of the image before it's discarded.
Don't break animations
A GIF might be animated. Converting one to a still image would destroy the motion, so GIFs are the exception: they skip optimization and are stored exactly as uploaded.
When you automate something, spotting these "this needs to be an exception" cases is what keeps the automation trustworthy.
Choosing where files are stored
Separating public from private
Images go into object storage (a service dedicated to holding files), but where they land depends on what they're for.
Blog and news images. Anyone with the URL can view them
Things like appliance manuals in PDF form. Only delivered through an authenticated request
We didn't want guest-facing documents to be one leaked URL away from being visible to everyone. They live in private storage and are handed out only to authorized people through a dedicated delivery path.
We also verify, immediately after saving, that a file really did land in the intended location. That guards against a misconfiguration dropping a private file into public storage. If it's not where we expected, we delete it right away and raise an error.
Images in use can't be deleted
Deleting an image that an article is using would leave a broken image on a live page. So before deleting, we check whether the image is referenced by any article, and refuse the deletion if it's in use.
Optimizing at display time too
On top of the optimization at save time, we also serve images sized to the viewer's screen at display time. Phones get a small version, large displays get a large one, automatically.
We also made image descriptions (alt text) editable in the dashboard in all four languages. That's the text read aloud for visitors using a screen reader, and the text shown if an image fails to load.
Wrapping up
Three points anchored the image pipeline.
- Inspect the contents — don't trust the extension or the declared format; read the file and confirm
- Uploading is the whole job — resizing, format conversion, and location-data removal all happen automatically
- Separate storage by purpose — what's safe to publish and what needs authentication are physically apart
Drafts, Scheduled Publishing, Multilingual — an Operator-Friendly Flow
How the publishing flow and multilingual rollout work.
A Rich-Text Editor with Safe HTML — Double Sanitization Against XSS
How we guarantee the safety of the article body.
Blog and News Run In-House — a Self-Built CMS for the Rental Site
For the design of the dashboard as a whole, see this hub article.