Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

devchops's avatar

How to organise/structure all vue components?

Im trying to use vue in a "traditional" laravel app (non SPA), and have followed Jeffery Way's workflow of swapping vue components in my blade files, so far, so good.

What I have noticed is that my entry-point main.js file will grow huge since it will be calling all the component .vue files for every single page. At the moment, I only have a handful of pages, but this will eventually grow to around 30+ pages. So even if a visitor only visits the homepage, the main.js file loaded includes components required for all 30+ pages.

One approach I think may work for me would be to Lazy Load the components only when they are required, but I have not managed to get this working correctly. I swapped over to using webpack just to try this out and no luck so far. Will this solve my issue, and if so, are there any tutorials on this?

Is there a simpler approach that can keep things organised and lightweight at the same time? How do you folks structure your vue components in non-spa apps? I'd appreciate any advice.

0 likes
15 replies
wheeler's avatar

I'm in the same boat. I'm going to end up with dozens of components and many of them are specific to only certain parts of the app, ie. an admin specific component with further nested components designed for only one part of the app.

I'm thinking that the only solution in a non-SPA app is either roll with a monolithic js file, or break Vue up into several instances. I will be going with the latter to try and keep my user facing code as snappy as possible.

devchops's avatar

Thanks @wheeler - At the moment im on headed towards having one monolithic JS file. I never thought of breaking up the vue instances - however I do have quite a bit of common code in my main.js entry-point, so breaking up into several vue instances may mean duplication of code in some instances.

I wonder if everyone else moving around with gigantic js files as well?

topvillas's avatar

Don't be drawn into the SPA trap if what you really have is web site (even if SPAs sound sexy). You may have several apps that sit under the umbrella of a single domain and share a common navigation. It's hard to say without more detail.

devchops's avatar

Just to clairfy, Im not going the SPA route at the moment.

Its a fairly typical business app that tracks customers, sales, orders, generates reports etc. However, I am making use of vue-strap and a few other vue.js resources (using import statements) which seem to bloat up the main index file since everything is included here.

Ive structured my individual pages that do need a bit of JS logic as separate .vue files (e.g orders.vue, customers.vue etc), however when it gets compiled down, its just one large monolithic JS file that is loaded for every page.

Id like to keep the app lightweight and only load what is being needed on any particular page.

lee__mason's avatar

you could take a look at a project im working on which auto adds the components template files by scanning the page content and looking for component names in a middleware: https://github.com/fluentkit/fluentkit/blob/master/app/Http/Middleware/AddComponents.php.

ive build a class which mimics the SystemJs javascript api, and then the middleware adds the compoenents before the closing body tag.

templates are then placed inline, but you dont have to keep track of it all, just register the components and they handle themselves.

2 likes
EventFellows's avatar

I've made the experience that a monolytic js file especially looks scary in terms of file size during development but when gulp is run with --production flag it looses 70% of its size making it lessof a concern.

1 like
devchops's avatar

@EventFellows - that's what ive been going with for now... production does drop the file size significantly, and since this is a static asset, it can be cached on client-side for subsequent requests.

@no12pixels - Im intrigued by this approach and will definitely give it a go sometime.

  • Is there a special way to declare the or structure the Vue components?
  • Would this work for nested components?
  • Also, if the components are dumped inline into the page, how does this affect blade rendering the {{ }} tags since Vue uses the same syntax? (or does step run after the blade tags have been processed on the server?)
lee__mason's avatar

@devchops if you look at the middleware its a while(true) loop, so it adds components, then "resets" the "new elements" var to true, so the while loop runs again until all components have been added.

the vue syntax is you choice, either use @{{ var }} to tell blade to ignore them, or change them in the view config to something else.

personally i use @{{ var }} syntax, its still pretty readable and distinct.

structuring isnt really a problem either, there just declared within tags.

truetamtam's avatar

In my case i maintain separate files, which i include according to a specific routes.

...
mix.browserify('bakery.js');
mix.browserify('otherbakery.js');
...
Slothlike's avatar

I'm really glad I came across this thread. Here's what I've been doing. I hope someone can point out a better way. If not, I hope this helps someone who was like me a while ago. This is probably a lengthy version of what @truetamtam is saying. I'm using Vue 2.0 and Laravel 5.3.

It's my understanding that the order of things matters.

  1. Bootstrap.js
  2. Your component declarations
  3. Your Vue instance

Step 1 is to run webpack on bootstrap.js separately from app.js

// Comment this line in app.js
// require('./bootstrap');

// Webpack it in gulpfile.js
mix.webpack('bootstrap.js')
      .webpack('app.js')
      .version(['css/app.css','js/bootstrap.js','js/app.js]);

Step 2 setup your app.blade.php to pull in bootstrap.js and allow for the inclusion of your components using @yield

<!-- Scripts -->

<script src="{{ elixir('js/bootstrap.js') }}"></script>
@yield('components')
<script src="{{ elixir('js/app.js') }}"></script>

<!-- I also have a generic "Scripts" section sometimes -->
@yield('scripts')

Step 3 Now you can keep global components like alerts, modals and other things use across your entire site inside app.js and then make route specific components and create a specific .js file for them.

// resources/assets/js/cms_components.js
Vue.component('post-form', require('./components/posts/Form.vue'));

// Form.vue will import any other required components, I don't need to list all of them here.

Step 5. Add your component js files to gulpfile.js.

// Webpack it in gulpfile.js
mix.webpack('bootstrap.js')
      .webpack('app.js')
      .webpack('cms_components.js')
// Any other route specific components
      .webpack('admin_components.js')
      .version(['css/app.css','js/bootstrap.js','js/app.js','js/cms_components.js','admin_components.js']);

Step 6. Whichever route needs your components you create a "components" section and point to the js

@extends('layouts.app')
@section('content')
    <!-- This is a global component -->
    <breadcrumbs :path="{{ $breadcrumbs }}"></breadcrumbs>
    <div class="container-fluid">
        <div class="row">
            <div class="col-xs-12">
                <!-- This component only works if you've included it in the components section below -->
                <post-form :post="{{ $post }}" tags="{{ $tags }}"></post-form>
            </div>
        </div>
    </div>
@endsection

@section('components')
    <script src="{{ elixir('js/cms_components.js') }}"></script>
@endsection

I'm no expert, this is the only way my old brain could figure it out.

15 likes
okmkey45's avatar

Well, since you are trying to make a "traditional" application with laravel and vue, I think that you should make JS files for every view. I don´t see the point to build one single main.js with the whole javascript code for your app and, like you said, eventually with 30 views, that main.js would grow a lot.

My workflow (maybe not perfect, still learning) is DEPENDS. Your app has simple codes, simple components, simple views, simple forms, most of the views use the same components; for me, this could be SPA. But if your app has multiple and very different views, complex codes, maybe big libraries or packages are needed, split in several JS files (user-view.js, report-view.js, etc).

I hope it helps.

AerialServant's avatar

@Slothlike I think your solution is the best one I've seen! I've been searching around for hours on how to better organise components using webpack. There should be a Vue-/Lara-cast on this topic specifically. It could have saved me a lot of time and trouble. But now, everything is working just how I like, and I have you to thank!

2 likes
BarisLeenders's avatar

The last few days I have been looking for a solution that would let me use Vue components in my Laravel application based on the current route/view. I often use Vue components in my non SPA applications and I always ended up with a huge .js file(>700kb). This file included pretty much all my JS(jQuery, Bootstrap, Vue, Axios etc.). This file will keep on growing in size the more Vue components you use. Most components I only use on a routes/views so it doesn't make sense to have load them all the time.

I came up with a solution that works for me, it's not done but it will do for now. I created small a facade:

<?php namespace Blaris\Core;

/**
 * This class is the main entry point of Blaris. Usually this the interaction
 * with this class will be done through the Blaris Facade
 *
 * @license MIT
 * @package Blaris\Core
 */
use Blaris\Core\Composers\VueAssetsComposer;

class Blaris
{
    /**
     * Laravel application
     *
     * @var \Illuminate\Foundation\Application
     */
    public $app;

    /**
     * Create a new instance.
     *
     * @param \Illuminate\Foundation\Application $app
     *
     */
    public function __construct($app)
    {
        $this->app = $app;
    }

    /**
     * Adds a Vue Component to the view
     *
     * @param array $components
     */
    public static function requireVueComponent($components = [])
    {
        $c = new VueAssetsComposer($components);
        $c->compose();
    }
}

And also a Composer class:

<?php namespace Blaris\Core\Composers;

use View;

class VueAssetsComposer
{
    protected $_assets = [];

    public function __construct($assets = [])
    {
        $this->_assets = $assets;
    }

    public function compose()
    {
        $assets = $this->_assets;

        foreach ($assets as $key => $asset) {
            if(file_exists(public_path('js/vue/components/'.$asset.'.js')))
                continue;
            array_forget($assets, $key);
        }

        View::composer('core::layout.assets',
            function( $view ) use ($assets){
                $view->with('assets', $assets);
            }
        );
    }
}

Afterwards I created a blade file that is included in my main blade file

@include('core::layout.assets')

In this blade file I add all the .js files I need to run my application. First I include the important libraries like Vue, Jquery, Axios etc (/js/app.js). Secondly I add all the Vue components I included in the controller method of the route. Lastly I add a .js file that only includes my Vue app.

const app = new Vue({
    el: '#app',
    components : {
    },

    data () {
        return {
            user : Object
        }
    },

    mounted(){
    },
});
<script src="{{ asset('/js/app.js') }}" type="text/javascript"></script>
@if(isset($assets))
    @foreach($assets as $asset)
        <script src="{{ asset('/js/vue/components/'.$asset.'.js') }}" type="text/javascript"></script>
    @endforeach
@endif
<script src="{{ asset('/js/vue/app.js') }}" type="text/javascript"></script>

This way I can easily include Vue components per route. Just call the requireVueComponent method on the facade in the controller method of the route:

Blaris::requireVueComponent(['AddOrderItem','ProductList']);

Now I can use the Vue component in any blade file used on this route aslong it's within #app:

<add-order-item></add-order-item>
Vervum's avatar

@BarisLeenders Great inspiration, really!

Unfortunately I had some trouble with your code. But a few modifications later I got it running. The main problem was that I used single file Vue templates (*.vue) with HTML markup and a script section in it instead of defining the template in a .js-file. The .js-files have the backdraw that they have to be included in my mix-file and the HTML markup in the template element of the JS data isn't properly highlighted and formatted by my IDE. So I really want to continue using my .vue-files (which is also recommended).

Thus I applied some modifications to achieve this and gain some comfort, too.

But first some infos about the setup:

php: 7.1.3
laravel/framework: 5.6.34
php-unit/php-unit: 7.3.2
symfony/class-loader: 3.4.14
symfony/*: 4.1.3

vue: 2.5.17
vue-loader: 13.7.3
vue-template-compiler: 2.5.17
bootstrap: 4.1.1
jquery: 3.3.1
laravel-mix: 2.1.14
webpack: 3.12.0
webpack-dev-server: 2.11.3

My approach reduces the number of .js-files needed to only one which registers all the Vue components (of course the .vue-files are still needed).

For an overview I created a list of files you will need to modify or create:

app/OSVue/composers/VueAssetsComposer.php (create - name and path may be changed)
app/OSVue/OSVue.php (create - see above)
ressources/assets/js/components.js (create)
ressources/assets/js/vue.js (create)
ressources/assets/js/app.js (modify)
webpack.mix.js (modify)
ressources/views/layouts/assets.blade.php (create)
ressources/views/layouts/app.blade.php (modify)

Here is my code based on your work:

First the composer (the prefix 'OS' comes from my app, ignore it):

namespace App\OSVue\Composers;

use Illuminate\Support\Facades\View;

class VueAssetsComposer
{
    protected $_components = [];

    public function __construct($components = [])
    {
        $this->_components = $components;
    }

    public function compose()
    {
        $components = $this->_components;

        foreach ($components as $slug => $component) {
            if(file_exists(resource_path('assets/js/components/'.$component.'.vue')))
                continue;
            array_forget($components, $slug);
        }

        View::composer('layouts.assets',
            function( $view ) use ($components){
                $view->with('components', $components);
            }
        );
    }
}

The main difference is that the composer now requires an array of key/value pairs. The key defines the 'slug' (or the name to be used in the HTML markup later on) and the value is the name of the .vue-file without suffix .vue. I will show the usage later.

And here is the class file that is to be used in the controller to declare the use of any component (there should be no differences to yours except for naming):

namespace App\OSVue;

use App\OSVue\Composers\VueAssetsComposer;

class OSVue
{
    /**
     * Laravel application
     *
     * @var \Illuminate\Foundation\Application
     */
    public $app;

    /**
     * Create a new instance.
     *
     * @param \Illuminate\Foundation\Application $app
     *
     */
    public function __construct($app)
    {
        $this->app = $app;
    }

    /**
     * Adds a Vue Component to the view
     *
     * @param array $components
     */
    public static function requireVueComponent($components = [])
    {
        $c = new VueAssetsComposer($components);
        $c->compose();
    }
}

In any controller you load components to be used in the view this way (here a snippet from the index method of the home controller):

    OSVue::requireVueComponent(['example-component' => 'ExampleComponent', 'example-component2' => 'ExampleComponent2']);
    return view('home');

The components then can be included in the corresponding blade file normally by using <example-component></example-component>. The component itself is defined in the file ExampleComponent.vue as recommended by the Vue docs.

Ok, now to the glue: The .js-file that manages the component registration. I named it components.js and placed it where app.js is located. This is it:

$encodedComponents = document.getElementById('include-components').dataset.components;
$components = JSON.parse($encodedComponents);
for (var $slug in $components) {
    Vue.component($slug, require('./components/' + $components[$slug] + '.vue'));
}

What does it do? The components to be registered are provided as an HTML data-* option named data-components to the script tag with the id 'include-components' (you will see this later). The data is json encoded which has to be reverted. Well the rest is ... obvious.

You will have to include this .js-file into your mixfile webpack.mix.js together with your app.js and vue.js otherwise it won't work ('require' will fail):

mix.js('resources/assets/js/app.js', 'public/js')
    .js('resources/assets/js/components.js', 'public/js')
    .js('resources/assets/js/vue.js', 'public/js')
    .sass('resources/assets/sass/app.scss', 'public/css');

My vue.js (located in the same directory as app.js and components.js) looks like this:

const app = new Vue({
    el: '#app'
});

And my app.js:

require('./bootstrap');
window.Vue = require('vue');

Don't forget to tell mix to do its job:

npm run dev

Now where is the components.js script loaded? This happens in the newly created assets.blade.php (the 'defer' statement is important otherwise the script will be executed before the DOM is ready which will cause errors like 'unknown element #app'):

<script src="{{ asset('js/app.js') }}" defer></script>
@if(isset($components))
    <script id="include-components" src="{{ asset('js/components.js') }}" data-components="{{ json_encode($components) }}" defer></script>
@endif
<script src="{{ asset('js/vue.js') }}" defer></script>

Here the script tag gets an option id="include-components"to access the element in the components.js to retrieve the data-components item which holds the json encoded list of key/value pairs of components.

Finally this blade file is included in the main app blade file (app.blade.php). Replace the reference to the app.js script by this:

    <!-- Scripts -->
    @include('layouts.assets')

Now it works perfectly fine for me and allows using .vue files for defining components. It reduces the clutter of having a lot of .js files and of having mix to tell there are a lot of .js files.

Thanks for the great work. I hope my modifications will help someone.

Vervum's avatar

@Vervum Oh, I have a little modification to myself :))

Instead of editing the controller files to register the components I switched over to have the same PHP in the first line of the corresponding blade file.

Have a look: Instead of composing the components in the controller like this (e.g. the index method of the home controller):

OSVue::requireVueComponent( [ 'example-component' => 'ExampleComponent', 'example-component2' => 'ExampleComponent2' ] );
    return view('home');

I just add a new first line to my corresponding blade file (e.g. the home.blade.php) using a @phpdirective to do the same thing:

@php(
        \App\OSVue\OSVue::requireVueComponent(['example-component' => 'ExampleComponent', 'example-component2' => 'ExampleComponent2'])
)
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">Dashboard</div>

                <div class="card-body">
                    @if (session('status'))
                        <div class="alert alert-success" role="alert">
                            {{ session('status') }}
                        </div>
                    @endif

                    You are logged in!

                        <example-component></example-component>
                        <example-component></example-component>
                        <example-component2></example-component2>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

This makes it a lot easier. The only place to define what happens is JUST your blade file! If something changes: No need to modify the controller. Just the blade (and may be the component).

Nice!

Please or to participate in this conversation.