Delayed Display of Isolated PDFs with Fly-replay and Livewire

A space balloon creature with 6 hands. 3 On the left handles a remote, a button, and a droplet. 2 on the right holds a file and sifts through a record of documents separated into two boxes. The background is set in a gradiently pink and dark outerspace.
Image by Annie Ruygt

In this article we’ll tackle file isolation in a multi-region Laravel Fly App, and display PDFs in a deferred manner. Run your Laravel app with Fly.io, it takes only a few minutes!

Running our Laravel application close to our users reduces geographical latency. What’s more, with Fly.io, we get to easily do global deployments with just a few commands!

However, as we’ve established in Taking Laravel Global, files stored in one region are not automatically available to instances in other regions.

To address this region-isolation dilemma, today we’ll use the fly-replay response header to direct file retrieval requests to the correct regional instance. Alongside which we’ll use Livewire’s wire:init to speed up initial loading of pages displaying our files.

Here’s a reference repository you can read and a demo page you can inspect.

The Problem

Let’s say we have a Laravel Fly App with instances running in AMS, FRA, and SIN.

This Laravel application stores PDFs in each instance’s local storage folder and allows users to view these PDFs in the browser window.

If a first_title.pdf file was stored in the AMS region of our Laravel Fly Application, by default, this PDF file will only be accessible to users accessing the AMS instance, but not from instances in FRA or SIN.

Furthermore, even if instances from different regions get to access files stored in other regions, cross-regional data transfer can affect the hold up in pages displaying the file.

Solution

When it comes to accessing regional-specific storage, we simply need to redirect requests and talk to the right instance containing a requested PDF.

We can easily do so by instructing Fly.io to redirect the request to the correct regional instance with its fly-replay response header.

Afterwards, to speed up the initial load-up of the page displaying the PDF file, we can use Livewire’s wire:init directive to defer loading our PDF file until the page has completed loading.

Locally stored, DB tracked, Replay Available

In order to make use of fly-replay, we need to know the region our PDF file is stored in.

Fly.io provides us a FLY_REGION environment variable which we can use to identify the region our instance is deployed in. We can add this in our config/app.php:

'fly_region' => env( 'FLY_REGION' ),

Then keep track of this region in the PDF details we store in our database:

Multi-region instances benefit greatly with talking to databases in close proximity. Check out running your Laravel Fly App with Multi-region MySQL or SQLite.

DB::table('file_records')->insert([
    'full_path' => 'storage/uploads/'.$fileName,
    'region_id' => config('app.fly_region')
]);

Storing a first_title.pdf file above should give us the following row in our database:

Make sure to persist stored files through the use of volumes. Here’s a quick guide to persist data on the storage folder.

full_path: "storage/uploads/first_title.pdf",
region_id: "ams"

With the above setup, we now have a record of our stored PDF, and the region it is stored in. We can start using fly-replay to forward retrieval requests to the right instance to get our first_title.pdf file!

Fly.io ❤️ Laravel

Fly your servers close to your users—and marvel at the speed of close proximity. Deploy globally on Fly in minutes!

Deploy your Laravel app!

Talking to the right instance with Fly-replay

To make our first_title.pdf file accessible outside of the AMS region, we’ll have to make sure all retrieval requests for the file “talk” to our instance running in the AMS region.

If the current instance’s region is not the same as the region_id recorded for first_title.pdf, we stop processing the request in the current instance, and instead return a fly-replay response header containing the region to forward the request to.

For visibility on the whole picture, you can read this Controller file for reference.

  // Decide replay
  if( $pdfDetails->region_id != config('app.fly_region') ){     

      // Replay to identified region
      return response('', 200, [
          'fly-replay' => 'region='.$pdfDetails->region_id ,
      ]);

  }else{

      // FileName
      $fileName = explode('/', $pdfDetails->full_path);
      $fileName = $fileName[(count($fileName))-1];

      // Accessible File Path
      $filePath = Storage::path( $pdfDetails->full_path );

      // Respond with File
      return response()->file( $filePath );

  }

Once the response header is returned by the application instance, Fly.io’s proxy layer will immediately recognize the fly-replay response header. It will then replay the request to the region specified in its region=<region_id> value.

Displaying PDF iframes with wire:init

Now that PDF files stored in different regions are accessible from the rest, we can proceed with displaying our PDF files with the use of iframes.

We’ll ask our iframe to retrieve the file from a route containing the file retrieval logic specified in our section above.

<iframe src="{{ URL::to('/files/show/'.$recordId ) }}"></iframe>

Placing this iframe tag in a page will hold up page load until our file gets successfully loaded. We can easily avoid this hold up by using Livewire’s wire:init directive.

Let’s create a Livewire component to defer loading our iframe tag:

Here’s the whole picture for our Livewire view and Livewire Component.

And a bonus reference on initializing and updating our Livewire component with a PDF record to render.

# In the Livewire View
<div id="showFileComponent">
   @if( $loadAllowed==true  )
      <iframe src="{{ URL::to('/files/show/'.$recordId ) }}"></iframe>
   @else
      Loading PDF
   @endif
</div>

In the case above, we only load the iframe when the public attribute $loadAllowed is true.

We’ll initially set our $loadAllowed to false, and only change it to true through the method allowFileLoading:

# In the Livewire Component
class ShowFile extends Component
{
    public $recordId;
    public $loadAllowed = false;

    public function allowFileLoading()
    {
        $this->loadAllowed = true;
    }

And finally, let’s add in wire:init="allowFileLoading" to tell the Livewire component that once it completes rendering, it needs to call the allowFileLoading method.

# In the Livewire View
<div id="showFileComponent" wire:init="allowFileLoading">

As seen above, calling the allowFileLoading method updates the $loadAllowed to true. Doing so loads the iframe tag which calls our Laravel route files/show/{recordId}.

Discussion

Running multi-region instances of our Laravel application means an effort from us to empathize on and close geographical latency for our users across the globe.

At the same time, it also means addressing regional-isolation of files and providing file-accessibility to our users.

Today, we addressed file isolation by talking with the right instance using fly-replay, and improved loading of pages displaying these cross-regional files through the use of deferred displaying with wire:init.

Of course, this article only focuses on accessibility for file-isolation, there are other equally important points for consideration.

One is Database consistency and performance optimization. We have guides to address those with multi-region MYSQL with PlanetScale, and multi-region SQLite with LiteFS.

For a checklist to mull over, further reading is advised on Taking Laravel Global.