DanielRønfeldt's avatar

Force file downloads in Laravel + Inertia + Vue?

As the title says, I'm trying to figure out how to force file downloads in a Laravel 10 project running Inertia with the Vue 3 stack and the Composition API.

The solution that LaryAI is suggesting involves using the Inertia.get() method, which is triggering an error saying that Inertia is not installed, which I believe is kinda silly since my whole project is Inertia-driven and working properly. And therefore, I'm looking for a solution that doesn't make use of the Inertia.get() method.

Thanks!

0 likes
6 replies
LaryAI's avatar
Level 58

To force file downloads in a Laravel project using Inertia with Vue 3 and the Composition API, you can create a route that handles the file download and use the response()->download() method to force the download.

Here's an example of how you can achieve this:

  1. Define a route in your web.php file that points to a controller method:
Route::get('/download-file/{filename}', [FileController::class, 'download'])->name('download.file');
  1. Create a FileController with a download method that handles the file download:
use Illuminate\Support\Facades\Storage;

class FileController extends Controller
{
    public function download($filename)
    {
        $path = Storage::disk('public')->path($filename);
        
        return response()->download($path);
    }
}
  1. In your Vue component, you can create a link that triggers the file download:
<template>
    <a :href="downloadUrl" download>Download File</a>
</template>

<script>
import { computed } from 'vue';
import { useRoute } from 'vue-router';

export default {
    setup() {
        const route = useRoute();
        const downloadUrl = computed(() => {
            return route.resolve({ name: 'download.file', params: { filename: 'your-file-name.ext' } });
        });

        return {
            downloadUrl,
        };
    },
};
</script>

Make sure to replace 'your-file-name.ext' with the actual filename you want to download.

This solution bypasses the need to use the Inertia.get() method and directly triggers the file download using a regular route and the response()->download() method.

DanielRønfeldt's avatar

Based on LaryAI's solution above, I managed to successfully forward the correct route to the controller.

But now, despite the fact that I already created the symbolic link from public/storage to storage/app/public, the file cannot be downloaded, and I'm getting the The file "/Users/user/code/demo/storage/app/public/test.pdf" does not exist Exception.

I DID NOT modify the default public disk configuration within config/filesystems.php, is there anything I need to update in there?

Any thoughts?

Thanks a lot!

flavioafa's avatar

@DanielRønfeldt Hello! Try this!

fetch(
            route('documents.pdf', {
              id: id
            })
          )
            .then((res) => res.blob())
            .then((blob) => {
              const file = window.URL.createObjectURL(blob)
              window.location.assign(file)
            })
1 like
sididev's avatar
sididev
Best Answer
Level 8

@DanielRønfeldt Hello there, inertia.js is designed to work with Single Page Applications (SPAs) by using AJAX requests to dynamically load content without refreshing the entire page. When navigating in an Inertia.js application, link clicks and form submissions are intercepted, and Inertia makes XHR (AJAX) requests to fetch new content in JSON, which it then uses to update the page dynamically.

However, this approach does not work for file downloads because when a file is downloaded, you expect the browser to process a binary content response and open the standard download dialog box, which is not the behavior of a standard XHR request.

By setting window.location.href with the download URL, you are instructing the browser to make a regular HTTP GET request to that URL, outside the context of Inertia.js. This is similar to what would happen if you entered the URL directly into the browser's address bar or clicked on a link without Inertia.js intervention.

The server then responds with HTTP headers indicating to the browser that this is a file download (typically Content-Disposition: attachment; filename="file-name.pdf"), and the browser opens the download dialog box, allowing the user to save the file locally.

Here is the solution : create a dowload button

// button
<button @click="download(file)"> download </button>

// function
function download(file) {
    const url = route('download', { info: file })
    window.location.href = url
}

2 likes
gguio's avatar

Hey! I've just hit this exact issue and your solution looks very cool. But I have a lot of ids in each request. Is there a way to pass them as we can do in router.get(url, ids)?

nazrul-dev's avatar
const downloadEntity = (e, item) => {
    e.preventDefault();

    fetch(route('file.download', item?.id))
        .then((res) => res.blob())
        .then((blob) => {
            const fileURL = window.URL.createObjectURL(blob);

            // Buat elemen <a> dan set atributnya
            const link = document.createElement('a');
            link.href = fileURL;
            link.download = item?.filename; // Ganti dengan nama file yang sesuai, jika diperlukan

            // Tambahkan elemen ke body untuk dapat diklik
            document.body.appendChild(link);

            // Klik elemen <a> untuk memulai download
            link.click();

            // Hapus elemen <a> setelah digunakan
            document.body.removeChild(link);

            // Hapus URL objek setelah selesai
            window.URL.revokeObjectURL(fileURL);
        })
}

Please or to participate in this conversation.