Pébéo Global

Pébéo Global

Nuxt.jsLaravel
Pébéo is a global artistic supplies company based in Paris, France. They needed an update to their consumer site that could link into their established product management system and support translation in multiple languages. The Sprechen team and I put together a full CMS with Laravel and a lightning fast front end in Nuxt.js to create a fantastic user experience.

Templates to front ends

Templates to front ends

The designer for this site was very particular about how he wanted the site to look so commissioned a French company to build HTML templates that we could use to translate into a Nuxt application. A few days were spent taking apart the templates and working them into components. Each component could then have functionality built into it. For example, the Newsletter signup block would have a function to validate and send data to the API so people could sign up.

The interesting thing when adapting the company's work was figuring out how everything worked together. The CSS files that came with the templates were huge so it was difficult to make any changes without sifting through different classes to look at their effects. After working with the templates for a while, I began to better understand how they were built so adding features became easier and easier.

Translations

Translations

The entire site needed to be translated into 8 or so languages which meant a lot of data needed to be brought into the front end.

To accomplish this with smaller load times, I learned about and used async Nuxt config. Usually, nuxt.config.js is a hard coded file that specifies various settings about a Nuxt app such as meta tags, plugins being used and build settings. However, data can be loaded into the file from external sources by specifying it as async. This means that when the build command is run, axios HTTP requests can be sent to an API and the data return can be built into the config file. In this scenario, I could load in general translations such as titles, URLs and meta tags so they would be built into the site's code rather than being loaded when a user opens a page.

Async config

The catalogue

The catalogue

The established Pébéo catalogue is stored on a site called PIM created previously by Sprechen. I built out API routes for the new consumer site with the challenge of using a much older version of Laravel. Products are split into Universes > Ranges > Families > Products so most of the logic came into play when managing these relationships.


The catalogue itself can be filtered down through these levels using a sub navigation that adds to the URL parameters on the page. I had to optimise these calls as much as possible to cut down on load times which was mainly achieved by loading in the images after the page gets load.

Elastic Search

Elastic Search

Elastic search allows for a near instant search request on a site. This involves having either a service built into an application or an external one that indexes search results and handles returning results from the database. For this site, I used MeiliSearch as an installed service on the Ubuntu server alongside the backend Laravel application. I could then sync the product models with the service for incredible search speeds. When a search is done in Nuxt, an API request is sent to Laravel which then queries MeiliSearch for results and returns them to Nuxt.

Preparing models for Elastic Search

Preparing models for Elastic Search

For models to be imported into MeiliSearch, I needed to specify what fields from the model need to be searchable which in this case was quite a few. You can see here, I made the attributes that I wanted to be searchable into an array and also specified what should be filterable by the elastic search built in facet filters, in this case 'targets'.

Handling Search

Handling Search

The search functionality for the site needed to be able to return results for not only the catalogue but also for news articles, tutorials and other content. Because the catalogue of products are handled by an external application it presented a challenge in searching within all of the elements. To get around this, I added a set of models to the CMS that would use the PIM's database connection and give it the ability to sync them to Meili Search.


The search results would also need to be filtered which is challenging in itself due to how different all of the search result types are. The same filters would be not be able to be used for ecommerce products and tutorials for example. To get over this hurdle, I split the user interface into tabs for each resource with their own filters. This would allow the backend to differentiate between what results should be filtered when query parameters are present.

Product Pages

Product Pages

The product pages on the site load in data from a variety of API routes from both the PIM and CMS.

  • The product information and images come from the PIM.
  • PDF resources are loaded from the CMS.
  • Analytics for routes are sent to the CMS.
  • Images are loaded AJAX from the PIM.

While there is a lot of data required, because it is loaded from seperate sources asynchronously, it greatly reduces load times there would have been with server rendering.


There is also the interface for choosing colours and formats for a product. Technically, the page here is the family page where a family has a line of products with different colours and formats. What happens on this page is that lists of available colours and formats are calculated in the frontend for the family with each colour bubble and format linking to a different product code within the same family.

Product Colours

Loading Images

Loading Images

Images around the site are loaded asynchronously in some places and server side in others. This mostly comes into play depending on what other data is being loaded at the same time. With the product pages, images are loaded from the PIM so I built a component that would provide the PIM product ID to an API endpoint and receive image URLs in return. While the request is waiting to come back, a loading component is shown which is the flashing grey block you can see.

Image loader component

Social Media Feeds

Social Media Feeds

The clients wanted to have a feed at the base of the page with the latest post from their Instagram, Facebook, YouTube, Pinterest and Twitter accounts which I initially thought would require a huge amount of JavaScript and API queries to pull data from different sources. However, after some thought the Sprechen team and I came up with the following system.

  1. Store access tokens for each social channel for each locale.
  2. Run a cron command every few hours that would use said tokens to get the latest post from each channel.
  3. Save each post in a database table making the data uniform with a name, image URL, and post link.
  4. Build an API route to provide the latest posts for a locale to the front end.
  5. Should API calls fail there is no problem as there will be a backlog stored so fixes can be done.

This task would take a lot of different moving parts to produce but I managed to create a working solution.

Instagram post getter

Find a Stockist

Find a Stockist

A reseller finder was also needed so that customers can easily find the nearest shop that sells Pébéo products. This used a combination of Google maps and an API that uses Google's geolocation services so that a radius can be defined around a location to determine nearby places.

Location

Users also have the option for the application to automatically get their location using the browser geolocation API. If they allow it, it will get their current coordinates and send them to the CMS API where it can use the Google Geolocation API to determine the city the person is in before auto-filling the city textbox input.

Generating a sitemap

Generating a sitemap

A sitemap is an invaluable document that allows Google and other search engines to index your site more easily. However, because the site is mostly build using API routes with async calls, it is difficult to produce one as the routes are not permanent. To get around this issue, I instead had the CMS run through all of the resources in the database and generate a list of URLs for each locale. These can then be sent to the front end at build time for the package @nuxt/sitemap to generate a sitemap index and associated site maps with.

Sitemap generation

Building Pages

Building Pages

The client wanted a visual and flexible way to build out the pages on the site rather than just editing textboxes. I built an Elementor inspired block editor using Editor.js which provides a strong class based API allowing me to create different editable blocks with styling that matches the front end site for the admins to edit. Each block is its own class with its own data and functions to render the block and handle saving the resulting data. Below is an example of one of the blocks that provides a side by side image and caption.

Captioned image

Once data has been entered, editor.js outputs a large JSON array containing all of the different blocks and data as defined by me. This data can be looped through on the front end and different components can be rendered based on the block type.

Gallery

Gallery
Gallery2