Image Previews not displaying on Live site

What seems to be the problem:
Temporary images display locally, but not on live site. The images still upload, but are not displaying on-screen as intended. The previews look like they load, but nothing is displayed. I can see the blank image blocks in the preview area, along with each’s signed URL.

Console shows 404 errors for each image uploaded. Attempting to load the signed URL in a different tab results in a 401.

I’m unsure if this is intended or my issue, or how to correct if it is, or if it something else.

Any and all help appreciated. Cheers!

Steps to Reproduce:
Upload an image(s) on live server.

Are you using the latest version of Livewire:
“livewire/livewire”: “^2.0”,

Do you have any screenshots or code examples:
VIEW

<div class="w-full" x-data="{ progress: 0, isUploading: false }"
	x-on:livewire-upload-start="isUploading = true"
	x-on:livewire-upload-finish="isUploading = false"
	x-on:livewire-upload-error="isUploading = false"
	x-on:livewire-upload-progress="progress = $event.detail.progress">

	<label class="flex-col mb-2 text-sm font-bold text-gray-700" for="images">
		<div class="flex flex-row flex-no-wrap w-full h-16 sm:h-20 md:h-24 bg-gray-200 focus:outline-none cursor-pointer items-center text-center rounded border @error('images') border-red-600 @else border-gray-200 @enderror overflow-y-hidden overflow-x-scroll mb-3">
		
        		@if( ! empty( $images ) )	
				@foreach( $images as $image )
					@php
						$load = false;
						$ext = ['jpg','jpeg','png','webp','heic','heif'];
						if ( in_array( $image->guessExtension() ,  $ext ) ) {
							 $load = true;
						}
					@endphp
										
					@if($load)
					<div class="flex-none h-16 bg-no-repeat bg-center bg-cover mr-2 {{ $loop->first ? 'ml-4' : '' }}" style="background-image: url({{ $image->temporaryUrl() ?? '' }}); width: 100px;"></div>
					@else
					<div class="block w-16">
						<span class="text-sm text-red-600">
							<span class="uppercase">{{ $image->guessExtension() }}</span> is an invalid filetype. An image is required.
						</span>
					</div>
					@endif
				@endforeach
			@else
			<div class="block w-full">
				<span class="w-full self-center mx-auto @error('images') text-red-600 @else text-gray-700 @enderror text-gray-700 font-bold">Upload Image</span>
				@error('images')
				<p class="w-full text-red-500 mt-1 text-sm font-normal pb-2 hidden md:block">{{ $message }}</p>
				@enderror
			</div>							
			@endif														
		</div>
		<input wire:model="images" class="w-full px-3 py-2 text-base leading-tight border border-gray-200 bg-gray-200 focus:bg-white text-gray-700 rounded shadow appearance-none hidden"
			id="images" name="images" type="file" multiple />
	</label>
        <div wire:loading wire:target="images" class="w-full my-2 text-red-600 text-center font-bold">Uploading...</div>
</div>

CONTROLLER

namespace App\Http\Livewire;

use Image;
use Storage;
use Livewire\Component;
use Livewire\WithFileUploads;
use Illuminate\Validation\ValidationException;


class XXXXX extends Component
{
    use WithFileUploads;

    public $images = [];
    public $imageList = [];

    [...]

    public function goToStepThree()
	{

		if( empty( $this->images ) ) {
			throw ValidationException::withMessages( ['images' => 'At least one image is required.'] );
		}

		// Get rid of any old images
		if( count( $this->imageList ) > 0 ) {
			foreach( $this->imageList as $oldImage ) {
				Storage::delete( $oldImage );
			}
			$this->reset( 'imageList' );
		}

		// Resize and store each image
		foreach( $this->images as $image ) {
			$this->resizeAndStoreTheImage( $image );
		}

		[...]

	}

    protected function resizeAndStoreTheImage( $image ) 
	{
		// Set the new image's name
	    $filename = time() . '-' . mt_rand( 10000 , 1000000 );
	    $filename_w_ext = $filename . '.' . $image->extension();

		// Save original uploaded image
		$original_image = $image->storeAs( 'ticket-request-images' , $filename_w_ext );

		// Get the original uploaded image's details
		list( $original_image_width, 
			$original_image_height, 
			$original_image_type, 
			$original_image_attr 
		) = getimagesize( Storage::path( $original_image ) );

		// Define new sizes
		$newSizes = [
			'xs' => [
				'size' => 250,
				'quality' => 60
			],
			'md' => [
				'size' => 600,
				'quality' => 70
			],
			'lg' => [
				'size' => 1024,
				'quality' => 80
			],
			'xl' => [
				'size' => 2048,
				'quality' => 90
			],
		];

		
		$thumbs = [];

		while( $this_new_size = current( $newSizes ) ) {

			// Get the size key, i.e. 'xs','md','lg'...
			$key = key( $newSizes );

			// Create a dynamic variable for folder path
			${$key} = Image::make( Storage::path( $original_image ) );

			// If the height or width of image is greater than newSizes size
			if( $original_image_width > $this_new_size['size'] || $original_image_height > $this_new_size['size'] ) {

				// Resize images - constrain to the new size's size on either side
				${$key}->resize( $this_new_size['size'] , $this_new_size['size'] , function ($constraint){

			    	$constraint->aspectRatio();

				})->stream( 'jpeg' , $this_new_size['quality'] );

				// Set the location
				$location = 'ticket-request-images/thumbnails/' . $key . '/' . $filename . '.jpg';

				// Store the resized image
				Storage::put( $location , ${$key} );

				// Push the location to the thumbs array
				array_push( $thumbs , $location );

			}

		    next($newSizes);

		}

		// Add the location to the imageList array
		array_push( $this->imageList, $thumbs );

		// Delete original, non-resized image
		Storage::delete( $original_image );
	}

Hey, @whoisthisstud

Make sure to update .env app URL and assets URL in config/livewire.php

Thanks @skywalker, but I don’t believe that’s my issue.

APP_URL is set in both local and demo ENVs and ‘asset_url’ points to the APP_URL.
Works locally, but not on Forge/DO server.

Example of temp image URL returning 404 in dev tools, but 401 direct.

https://mysubdomain.mydomain.com/livewire/preview-file/K9L8uklSU5fVyppZrfWzw2OhV2IUzR-metaMjAxNSBIb25kYSBSYW5jaGVyIDQyMCA0eDQgQVRWIC0gMzIwMC5qcGc=-.jpg?expires=1614308360&signature=5e2883b0bb900733308e6060f6df1a721bd2047c85fe421efc7935107eb2b102

Timestamp is correct. Not sure where to begin in verifying the signature.

Hey, @whoisthisstud

401 means you are not authenticated, So make sure you are providing the right permission to the current user.

Make sure the livewire assets was published as well.

@skywalker

The upload form is a public form. Why would I need to provide authentication?

Also, do the local livewire assets not publish to Forge, through my git repo?

No need to authenticate anything if the upload states if in public.
For me. I don’t face any of this problem while deploying to forge.

I found this issue in GitHub it may help you

@skywalker

My composer.json file already includes the force publishing of Livewire’s assets.

"post-autoload-dump": [
     "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
    "@php artisan package:discover --ansi",
    "@php artisan vendor:publish --force --tag=livewire:assets --ansi"
]

I am grabbing the site url from the env file, in Livewire’s config, and this variable is set in Forge’s env file for that domain.

/*
    |--------------------------------------------------------------------------
    | Livewire Assets URL
    |--------------------------------------------------------------------------
    |
    | This value sets the path to Livewire JavaScript assets, for cases where
    | your app's domain root is not the correct path. By default, Livewire
    | will load its JavaScript assets from the app's "relative root".
    |
    | Examples: "/assets", "myurl.com/app".
    |
    */

    'asset_url' => env('APP_URL', 'https://xxx_clients-domain_xxx.com'),

What am I missing?

Is there something wrong with the way I’m calling the image? should I be including this in asset()?

<label class="flex-col mb-2 text-sm font-bold text-gray-700" for="images">
  <div class="flex flex-row flex-no-wrap w-full h-16 sm:h-20 md:h-24 bg-gray-200 focus:outline-none cursor-pointer items-center text-center rounded border @error('images') border-red-600 @else border-gray-200 @enderror overflow-y-hidden overflow-x-scroll mb-3">
    @if( ! empty($images) )	
      @foreach( $images as $image )
        @php
          $ext = ['jpg','jpeg','png','webp','heic','heif'];
          $load = in_array($image->guessExtension(),  $ext) ? true : false;
        @endphp
        
        @if($load)
        <div class="flex-none h-16 bg-no-repeat bg-center bg-cover mr-2 {{ $loop->first ? 'ml-4' : '' }}" style="background-image: url({{ $image->temporaryUrl() ?? '' }}); width: 100px;"></div>
        @else
        <div class="block w-16">
          <span class="text-sm text-red-600 uppercase">
            <span class="uppercase">{{ $image->guessExtension() }}</span> is an invalid filetype. An image is required.
          </span>
        </div>
        @endif
      @endforeach
    @else
    <div class="block w-full">
      <span class="w-full self-center mx-auto @error('images') text-red-600 @else text-gray-700 @enderror text-gray-700 font-bold">Upload Image</span>
      @error('images')
      <p class="w-full text-red-500 mt-1 text-sm font-normal pb-2 hidden md:block">{{ $message }}</p>
      @enderror
    </div>							
    @endif														
  </div>
  <input wire:model="images" class="w-full px-3 py-2 text-base leading-tight border border-gray-200 bg-gray-200 focus:bg-white text-gray-700 rounded shadow appearance-none hidden" id="images" name="images" type="file" multiple />
</label>

@skywalker, it just doesn’t make sense why it works locally, but not on Forge.
BTW, I’m running valet locally. Not sure if I shared that originally.

@skywalker
Problem was my nginx cache policy caching jpg|jpeg|png|webp
Removed caching for these and temp images displayed.

I would prefer to keep the other images cached, but not these temp images. Workaround?

Hey, @whoisthisstud

Take a look here to learn more about headers and caching in general