Symfony to VueJS with Algolia

Arnaud Ruckebusch
Selency Tech & Product
8 min readAug 23, 2019

--

Once upon a time, there was a world without any strong front framework.

Like a lot of websites, it was also the time of the Selency front-end development. Since our architecture was micro-services oriented (and using principles from the DDD pattern) with Php and Symfony we used to build our front with Symfony as well.

By the half of 2018, Symfony+Twig+JQuery was showing its own limits and it got time to step towards a big technical evolution: integrating a front framework in our code base.

Why moving away from a Symfony front-end?

Since the initial developments the Symfony front-end was working fine, we were able to manage our differents templates with twig and jQuery. Soon, however, came a time when new, awesome features, and ideas, needed something more adapted than a triple (or more) imbricated Symfony form, together with the 300 lines of jQuery needed to keep things smooth, user-friendly, and evolutive.

The change was driven by the need for uniformity among our several catalog pages. There were search-page results given by an old index stored in Algolia, catalog pages containing data from our databases, each with templates and filters that used to differ from the search-pages content, plus shop pages with database data featuring but yet another template system. Yes, it was a mess.

Thus came the first project. To start the inclusion of a front framework for our front-end. The long-shot idea behind the change is to completely replace the Symfony with a single VueJS application and to use our well-formed micro-services backend.

Weapon of choice

To clear things up, we’ll be using Algolia (there will certainly be another article about how and why we select Algolia over Elastic but that’s not the purpose of this one).

The advantages it provides, in addition to their high availability/scalability/ customization and performance indexing system, is a bunch of plugin in a lot of languages to accelerate your integration. (Faceting widget / pagination / custom routing / sorting etc…)

So the great mayhem of front framework has started for us, there is an eternal “battle” between the 3 big frameworks these days. As everyone knows the fight is between Angular / React / Vue.

The great mayhem of Front Framework was started — as was the arduous question to know which, of Angular, React, or Vue, was best. I already worked with Angular we didn’t think it would quite fit the goal of integrating a lightweight framework: This one was out. From there, it was basically between React and VueJS, and even though neither had our preference (both being well maintained and documented), VueJS documentation was slightly more appealing to me at the time.

Main discriminant factors were: internal resources experience, progression curve, and Algolia’s widgets freshness. Based on this — no suspense — We chose Vue (2.x version).

Start small

I had never worked with VueJS, and after some serious documentation-studying, the first thing I did was to create a simple VueJS component in a separated bundle of the Symfony application. That way it was clearer and easier to update the VueJS architecture if needed and to observe the behavior of VueJS into Symfony, including potential issues I would encounter later on in the process.

Integration should be done step by step and I couldn’t simply come in, smashing everything to push my new VueJS component.

I started small, I didn’t want a lot of external packages. The only ones were vue-instantsearch and two for the cookies and storage. Having so few external plug-ins was helpful at the start, because it allowed me to clearly understand what was happening.

This way I was able to set up the basic infrastructure, make clean and separated responsibilities, etc…

To keep things simple, at first, I kept the Symfony router to handle internal links. VueJS was making calls to routes via fosJSRouting for the beginning of the project.

VueJS is coming

After this was all set, it was high time to dive deeper into the proper development of the feature. I just needed three more things:

  • A webpack configuration
  • A hot module to reload the configuration
  • A debugger

The webpack configuration is pretty simple with the webpack-encore Symfony plugin it works like a charm.

var Encore = require('@symfony/webpack-encore');
Encore
.setOutputPath('web/build/')
.setPublicPath('/build')
.addEntry('app', '/pathToFile/main.js') .enableSingleRuntimeChunk()
.cleanupOutputBeforeBuild() .enableSourceMaps(!Encore.isProduction())
.enableVueLoader()
.enableVersioning(false);
module.exports = Encore.getWebpackConfig();

The hot module reload is already included in “encore” with the webpack-dev-server :

yarn encore dev-server --https --port 9000

If you don’t know about it, you should consider using it, it allows you to develop and dynamically reload the component in your browser. If the changes are significative and too many components are involved it will also detect it and reload the page for you. A must-have.

For the debugger, I will just use the chrome console and the VueJS plugin associated that is helpful.

An important thing to note is that all my modules architecture was written down on paper before the implementation to make sure it was well structured before coding. Writing down the architecture of your modules before the development process will save you loads of time and hassle.

Deep dive in your rework

So now I was ready to start efficiently, all our much-needed data was already in Algolia and I could start to integrate the different plugins needed from our main bundle dependency: https://github.com/algolia/vue-instantsearch

This was pretty straightforward thanks to Algolia’s widgets. The documentation was really easy to understand https://www.algolia.com/doc/guides/building-search-ui/what-is-instantsearch/vue/#using-widgets and the different widgets were customizable enough to fit our needs. They are all based on the “slot” elements that allow a full widget edition, you can find more resources on this here.

<ais-toggle-refinement attribute="isNegotiable" label="Negotiable">
<a slot-scope="{ value, refine}" @click.prevent="refine(value)">
<label>
<i :class="value.isRefined ? 'checked' : null"></i>
<input type="checkbox" value="negotiable">
<span>{{ $t('filters.nego_products') }}</span>
</label>
</a>
</ais-toggle-refinement>

Or even faster when the customization was not needed:

<ais-pagination></ais-pagination>

In as little as three weeks the old pages were unified under a single VueJS template with 4 components: Facets / Sorting / Pagination / List — Routing with facets was handled and our catalog had never been faster!

Also, I was pleased to see the progression curve with VueJS was not a myth: it’s intuitive and the documentation is well written, with tons of samples — though let’s face it, it would probably have been the same with React.

Implementation tips

In facts, nothing is perfect but everything should have the capability to evolve and grow easily, that’s in this way I’ve developed the VueJS integration.

I have read, tweaked, and experimented a lot to had the simplest base possible to allow further evolutions, I have used some best practices described in this article that I recommend you to read.

One thing I don’t see frequently is the usage of the v-model on a component. A component should be reusable and always manipulate the same kind of object. To bind it to your current model, you can attach your parent component data to a v-model.

<button @click="select(item)">Click me</button>
<script>
export default {
name: 'CustomSelect',
methods: {
select(item) {
this.$emit('input', item.id);
},
}
}
</script>

This way you only have to emit an event on ‘input’ from the child component to trigger its update. You will avoid to use too many events in your code and only use them when it’s needed.

<custom-select v-model="product.color"></custom-select>

One thing I didn’t address at first was the SSR (Server side rendering). Today search engines and social networks are always trying to crawl your pages, but they only see the javascript tags and it’s not ok for your SEO.
It’s important to implement it for any crawler (like google-bot) to have a version of your page that is well generated. As our concern was to make a quick move we decided to use prerender.io.

Prerender is rendering the javascript in a browser, saving the static HTML, and returning it to the crawlers as a custom SSR implementation would do. This without having to take time for a custom implementation.

Onboard your team

An often forgotten part of good code is equally good documentation. Integrating this new framework had me modifying dozen of files and changing loads of things in the architecture, so it was essential to document the feature with care.

The last thing you want, when every developer in your team starts to mess around with your code, is to let them do so without a proper understanding of the new architecture and code style. (Let’s be honest: a poorly-written doc is oftentimes a good excuse to skip it entirely.)

And you can do more, Selency has “tech features” meeting where the whole tech team comes to listen to other projects that each team as worked on, how they managed it, what’s in place now. You will find that kind of little meetings increases the level of global knowledge and lower the number of F* words in the office.

The last step is to make everyone else get hands on it!

Conclusion

Since then, any front feature that needs a minor modification and isn’t under VueJS is scoped to be reworked with it, we improve the website every day and it will come a day when VueJS will be the biggest part of the front-end. This goal achieved it will be effortless to move remaining Symfony pages to VueJS.

This kind of upgrades doesn’t need to be frightening. It does, however, need to be prepared and accepted by the whole team. You need to know your needs and embrace evolution.

I hope this short feedback will help you achieve your transition too. Thanks to the Algolia team for their support during the first steps of the changes. Also, thanks to all the Selency tech and product team (Clément Salaün, Julien Goldberg, Malik Rezigui, Maxime Drouet, Maxime Joyeux) that are now convinced with the benefits of the front framework and who put a lot of efforts to make the marketplace shine!

Go for it step by step it’ll work!

--

--

Arnaud Ruckebusch
Selency Tech & Product

Lead developer, athletic, engaged in what I do, consistently looking for improvements