Handling photo heavy sites in development

18 February 2026 by Ryan Cramer   0 Comments

Techniques you can use to keep lightweight development sites without all the photo/image overhead.

This is an admittedly esoteric topic for a blog post, but also something that I think many ProcessWire developers deal with at some point or another. Some sites get so large that it's not practical (or at least not convenient) to keep a full copy of it in a development environment. This post provides some simple answers to that situation.


Too many photos to reasonably clone

Many of the websites ProcessWire users work on can contain a huge amount of photos. That's certainly been the case here. Most recently it was biketours.com that has tens of thousands of photos, once counting all of the different size and format variations. It adds up to gigabytes worth of data. Even processwire.com has a sites directory that has a large number of photos.

Often the scale of image files is quite a bit more than is practical to keep a copy of in local development versions of the sites. When I clone a live site to my local environment, I copy /site/*.php, /site/templates/* and /site/classes/*, but NOT /site/assets/ (where all the images, caches, backups and sessions are), unless I want to spend the day transferring files and filling up my hard drive. I suspect that this is a common scenario with a lot of us.

Setting up local detection logic

There are some simple solutions to this problem that enable you to keep a lightweight development copy of a site without all the images, and that's what this post focuses on. You can place hooks in your /site/ready.php file that only apply to your localhost/development site. I usually have an if() statement like this that adds hooks for my local environment:

if($config->httpHost === 'localhost') {
  // local/dev hooks go here
}

You'd replace localhost with whatever your local dev environment hostname is, whether locally hosted, or a staging server. Another way you could do it is to have a file that only exists in your local environment, loaded by your /site/ready.php file:

$localFile = __DIR__ . '/ready-local.php';
if(file_exists($localFile)) include($localFile);

Loading images from live server, locally

Armed with a way to detect when we are in the local/dev environment, we can add a hook that will make it load images from the live server (rather than localhost):

$wire->addHookAfter('Pageimage::url', function(HookEvent $e) use($config) {
  $e->return = 'https://processwire.com/' .
    substr($e->return, strlen($config->urls->root));
});

In the above, you'd replace https://processwire.com/ with your live server URL (with a trailing slash). Below is a more verbose example of the same thing that covers a few more edge cases:

$wire->addHookAfter('Pageimage::url', function(HookEvent $e) use($config) {
  // specify the live site root URL, this time with NO trailing slash
  $siteUrl = 'https://processwire.com';
  $imgUrl = $e->return;

  // use local file if it exists
  $pageimage = $e->object; /** @var Pageimage $pageimage */
  if(file_exists($pageimage->filename())) return;

  // if imageUrl already points live, use it (not likely)
  if(strpos($imgUrl, "$siteUrl/") === 0) return;

  // update to point to image on live server
  $e->return = $siteUrl . substr($imgUrl, strlen($config->urls->root)-1);
});

With that hook in place, all the images pull from the live server rather than producing errors locally.

An .htaccess alternative

You can also do this directly from your .htaccess file if you prefer, in which case you would skip the PHP version above. You'd add the following somewhere around section 8 of the .htaccess file (after the RewriteEngine On):

RewriteCond %{HTTP_HOST} ^localhost [NC] 
RewriteCond %{REQUEST_URI} \.(jpg|jpeg|gif|png|webp|svg)$ [NC]
RewriteCond %{REQUEST_FILENAME} (^|/)site/assets/files/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*) https://processwire.com/$1 [L,R=302]

In the first line you'd replace localhost with your local/dev hostname, and the last line you'd replace https://processwire.com with your actual live site URL. Trying to parse out .htaccess logic can be a little cryptic, which is why I wanted to demonstrate the PHP version first.

Using full-size images for size variations that might not exist

What if you are working with different image sizes/crops than what is on the live server? Those will still get directed to the live server, and the full size image might exist, but the size/crop that you are working with might not. In that case, you would get a 404. To avoid that, you can redirect all sizes/crops to the full size image on the live server. The .htaccess rules would be identical to the above, except the last line:

RewriteCond %{HTTP_HOST} ^localhost [NC] 
RewriteCond %{REQUEST_URI} \.(jpg|jpeg|gif|png|webp|svg)$ [NC]
RewriteCond %{REQUEST_FILENAME} (^|/)site/assets/files/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)\.[0-9]+x[0-9]+.*\.(jpg|jpeg|gif|png|webp|svg)$ https://processwire.com/$1.$2 [L,R=302]

Replacing all images with a single placeholder image

There's also another strategy you can use, which is to replace all the images with a placeholder image. In my case, the placeholder image is just a 500x500 black-background PNG. This hook below replaces all of the images with that placeholder:

$wire->addHookAfter('Pageimage::url', function(HookEvent $e) use($config) {
  // if file exists locally then use it…
  $pageimage = $e->object; /** @var Pageimage $pageimage */
  if(file_exists($pageimage->filename())) return;

  // …otherwise use a placeholder image
  $e->return = $config->urls->templates . 'styles/images/placeholder.png';
});

Here's the .htaccess equivalent to the above:

RewriteCond %{HTTP_HOST} ^localhost [NC] 
RewriteCond %{REQUEST_URI} \.(jpg|jpeg|gif|png|webp|svg)$ [NC]
RewriteCond %{REQUEST_FILENAME} (^|/)site/assets/files/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule (.*) https://processwire.com/site/templates/styles/images/placeholder.png [L,R=302]

In the first line you'd replace localhost with your local/dev hostname, and the last line you'd replace https://processwire.com... with the URL to your placeholder image (either hosted locally or live).

Avoiding “file not found” errors in the admin

Whether replacing with images from the live server, or using a placeholder image, you'll still get errors in the admin when the page editor is trying to create thumbnail size variations of images. So you can add another hook that will prevent that:

if($config->admin) $wire->addHookBefore('Pageimage::size', function($e) {
  $pageimage = $e->object; /** @var Pageimage $pageimage */
  if(file_exists($pageimage->filename)) return;
  list($width, $height, /*options*/) = $e->arguments;
  $copy = clone $pageimage;
  $copy->setImageInfo([ 'width' => $width, 'height' => $height ]);
  $e->return = $copy;
  $e->replace = true; // prevents hooked method from being called
});

Hooking the httpUrl method (if used)

The Pageimage class has the url() method and property but it also has an httpUrl method/property. The main use case for httpUrl is if you are rendering an email or something that will be viewed separate from the server. But if you are using it for regular image rendering in your site, then you'd want to add another hook that will capture it too:

$wire->addHookBefore('Pageimage::httpUrl', function(HookEvent $e) {
  $pageimage = $e->object; /** @var Pageimage $pageimage */
  $e->return = $pageimage->url();
  $e->replace = true;
});

If using the .htaccess versions then you likely can get along without the above hook.


That's a summary of what I've found helpful in maintaining lightweight development copies of large websites with lots of images over the last 15 years. If you can think of any other tips that might be helpful on this subject please add them in the comments below. Thanks for reading!


Post a comment

 

PrevProcessWire 3.0.255 new main/master version

This post covers updates made to the core between ProcessWire 3.0.247 and 3.0.255. Included are more than 70 issue fixes and 175 commits. We'll zoom in on the numerous new features and improvements to the core for one of our best new versions yet!  More