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

robmpreston's avatar

Spark for a SPA? Start from scratch or build on what's there?

Thoughts? Tried adding Vue-Router, having issues, undefined errors constantly if I roll my own App attached to vue-router, despite including the mixin for spark.

Would I be better off just starting from scratch, making my app and making calls to the Spark backend functionality?

0 likes
19 replies
brianjorden's avatar

I'm starting to look into this right now as well. We're building a SaaS for companies that will need multiple users where the app is a SPA accessing a Laravel based back-end API, but not looking to serve up the main application even on the same web server for a few reasons. Any thoughts anyone has on this would be very interesting to hear.

jekinney's avatar

After browsing the files, not to deep mind you, probably use the spark API area and create my own views might be the simplest approach with a separate js file for the spa.

Or add the vue router to the spark/resources/asstest/js/vue-bootstrap.js run gulp and should have the router loaded.

robmpreston's avatar

After spending some time looking through things, I'm not crazy about the way the JS is setup. Everything about it seems counter to what I normally do. Currently starting from scratch and I'll implement the spark stuff on the frontend as I go.

tptompkins's avatar

Sorry for my ignorance, but what does "SPA" stand for? I'm assuming "single page application" after some Googling but not entirely sure if that's what you're referring to.

brajt's avatar

Any news on the router front? Did anybody managed to make Spark work with vue-router?

jekinney's avatar

Never really tried. Spark is opinionated enough it's kinda take it or leave it imo.

brajt's avatar

Thanks. After investigating things, I'll try to use Spark's internal routing system based on sparkHashChanged events.

brianjorden's avatar

I had some other projects/priorities pull me away for a while, but I'll be doing a deep dive into this probably starting this weekend or early next week. After a cursory look, I am happy to offload a lot of the subscription setup and maintenance to what Spark hopefully offers and fits for here. My expectation and intention is to then use Spark just to handle a subscription signup and management, but a completely separate user/login/registration and general application that simply does a quick check to verify subscription status on login. Would let me keep Spark pretty pure in it's fairly opinionated design without having to mess with that too much and lets us build our app exactly how we want it to end up.

garethdaine's avatar

I've gone with using default Spark functionality, specifically using the tab-state mixin and the sparkHashChanged event.

So, in my home component:

Vue.component('home', {
    props: ['user'],

    /**
     * Load mixins for the component.
     */
    mixins: [require('./../spark/mixins/tab-state')],

    ready() {
        this.usePushStateForTabs('.spark-settings-tabs');
    }
}); 

Then:

Vue.component('courses', {
    data() {

        return {

            test: ''

        }

    },

    ready() {
        //
    },


    events: {
        /**
         * Handle the Spark tab changed event.
         */
        sparkHashChanged(hash) {
            if (hash == 'courses') {
                this.getTest();
            }

            return true;
        }
    },

    methods: {
        getTest() {
            this.$http.get('/dashboard/test')
                .then(response => {
                    this.test = response.data;
                });
        }
    }
});

In my component, and finally setting up my primary layout and tab content as per Spark settings views.

Seems to work pretty well.

This is only basic at the moment, so I may come across issues as I build out functionality, but so far so good.

I can create individual views tied to specific Spark components, that pull through and render whatever data I want, but only load it once the hash has changed. Pretty decent stuff.

I'll probably look deeper into vue-router at a later date. Any questions, let me know.

1 like
3KyNoX's avatar

Everything here have been posted two years ago.

Could be great to have something working with Vue2

Thanks anyway

justinhartman's avatar

So I think I've managed to get @garethdaine's example operational using Laravel 5.8, Spark 8.0 and Vue2. I thought I would share it with you guys.

Start off by editing your home.js Vue component. As we are using the tab-state mixin and the sparkHashChanged event like in the example above, the path and methods have changed. Note that ready() has changed to mounted().

Here's what it should look like:

Vue.component('home', {
    props: ['user'],

    /**
     * Load mixins for the component.
     */
    mixins: [require('./../../../spark/resources/assets/js/mixins/tab-state')],

    mounted() {
        this.usePushStateForTabs('.spark-settings-tabs');
    }
});

You might need to play with the path location, specifically ./../../../ but the above worked for me.

Then, create your custom Vue component. For the sake of this example let's save this file as /resources/js/components/custom.js.

Vue.component('custom-index', {
    props: ['user'],

    /**
     * The component's data.
     */
    data() {
        return {
            custom: []
        };
    },

    /**
     * The component has been created by Vue.
     */
    created() {
        var self = this;

        Bus.$on('sparkHashChanged', function (hash, parameters) {
            if (hash == 'custom') {
                self.getAllcustom();
            }
        });
    },

    methods: {
        /**
         * Get all the custom information for the application using
         * the index route to your Custom Controller.
         */
        getAllcustom() {
            axios.get('/yourcontroller')
                .then(response => {
                    this.custom = response.data.custom;
                });
        }
    }
});

Note that the getAllcustom() method is meant to receive a JSON return object by calling the index() method from your Controller. Your Controller should end up looking something like this:

public function index()
{
    $data = $this->custom->all();
    return response()->json(
        [
            'custom' => $data
        ]
    );
}

Create a Blade for the Vue component. You can save this as /resources/views/components/custom.blade.php:

<custom-index :user="user" inline-template>
    <div>
        <div class="card card-default" v-for="item in custom">
            <div class="card-header">@{{ item.name }}</div>
            <div class="card-body">@{{ item.description }}</div>
        </div>
    </div>
</custom-index>

As the getAllcustom() returns an array of all our custom items you loop through them in Vue by using the v-for option.

Finally, you need to setup your Home Blade to use the spark-settings-tabs you defined in the home.js component and then call your tabbed content via a URL hash. Here's an example of how you implement it:

@extends('spark::layouts.app')

@section('content')
<home :user="user" inline-template>
    <div class="container">
        <div class="row">
            <div class="col-md-3 spark-settings-tabs">
                <aside>
                    <h3 class="nav-heading ">
                        {{__('Navigation')}}
                    </h3>
                    <ul class="nav flex-column mb-4 ">
                        <li class="nav-item ">
                            <a class="nav-link" href="#custom" aria-controls="custom" role="tab" data-toggle="tab">
                                {{__('Custom Items')}}
                            </a>
                        </li>
                    </ul>
                </aside>
            </div>

            <div class="col-md-9">
                <div class="tab-content">
                    <!-- Custom Items Listing Tab Page. -->
                    <div role="tabcard" class="tab-pane active" id="custom">
                        @include('components.custom')
                    </div>
                </div>
            </div>
        </div>
    </div>
</home>
@endsection

It took me a while to figure this out but thanks to @garethdaine's example and tinkering in the Spark source code I figured out how to pull this all together.

Hope this helps someone else!

3 likes
3KyNoX's avatar

Thanks @justinhartman - This is definitely a very interesting example.

Another way, I was about otherwise to transform Spark to a full API that won't serve any php/blade files for the frontend part, which will be managed by Quasar Framework in my case.

I'll post news on this topic as things going further.

Pro topic as well of the second task I'm trying to achieve is merging Spark & Nova. Find more on this topic if needed:

https://laracasts.com/discuss/channels/nova/laravel-spark-with-nova-make-sense

sandervanhooft's avatar

Ah now I understand this custom routing solution in usePushStateForTabs is why Laravel Dusk doesn't work with Spark's anchored urls directly (you'll have to programmatically click through the tabs).

Thanks @justinhartman !

sandervanhooft's avatar

@SANDERVANHOOFT - Hm, Spark is not the reason for Dusk not working.

When I paste <app_root>/settings#/subscription in my browser, I go straight to the right tab.

Getting off-topic here, will follow-up elsewhere if necessary.

Please or to participate in this conversation.