Cause 1: The image is smaller than its display space
If your theme shows images at 1200 px width and you upload an 800 px photo, the browser stretches it by 50% — and upscaling always looks soft. This is the number-one cause, and it's invisible while editing because the photo looks fine at its native size. Fix: find out what width the spot actually displays (theme documentation, or right-click → inspect) and upload at least that many pixels.
Cause 2: Retina screens want double pixels
Modern phone and laptop screens render 2–3 physical pixels per CSS pixel. A 400 px-wide logo in the layout needs an 800 px file to look crisp on those screens. This explains the classic "blurry on my iPhone, fine on my office monitor" complaint. Fix: for logos and small images, upload 2× the display width.
Cause 3: Your CMS re-compressed or downsized your upload
WordPress, Shopify and the site builders generate multiple sizes of every upload and often serve a smaller, more compressed variant than you expect. Huge uploads make it worse: a 4000 px photo gets resized with fast, quality-costing settings. Fix: upload at the target size (e.g. 1200 px content images, 1920 px heroes, 2048 px Shopify products), already compressed — give the CMS nothing to do.
Cause 4: The platform's own compression (social media)
Instagram, Facebook and the other networks re-compress everything server-side. The more your upload deviates from their target size, the more aggressive that step is. Fix: upload a JPG at exactly the recommended pixel size (e.g. Instagram 1080 × 1350) under 1 MB.
What never helps
Increasing DPI (screens ignore it), exporting at quality 100 (bigger files, same look), or uploading gigantic originals "to be safe" (triggers cause 3). Sharp images come from matching the pixel size to the display — nothing else.