Dec. 11, 2019, 9:06 a.m.
A few days ago I’ve finally released a new version of my website "I Ride Bicycle", which allows cyclists to plan routes in a different part of the world, put photos on a map, write descriptions using WYSIWYG editor.
In a series of articles, I’m going to share how different technologies were used to build this site.
There are three user roles — visitor, user, and admin. A visitor is someone who opens a website and navigates through its pages. The user is someone who is stored in a database and is able to log in and put some new content. Admin is someone who gets notifications about new content published, can publish/unpublish routes submitted by users, create landing pages related to specific regions, provide SEO friendly descriptions, etc.
I didn’t want to bother users to manually tag their routes with different locations (cities) when a route is created. Instead, I wanted to do it automatically based on points of the route. This meant I needed to have some kind of database with locations and their respective points on a map. This also meant I had to get this database somewhere.
SEO also was an important topic with the following requirements:
- fast loading (70–90 lighthouse test when no 3rd party code on frontend)
- total control of pages available for crawlers and their content
- sitemap
- schema.org
With all that in mind and some previous experience I decided to choose the following tech stack:
- Django. Basically, because we all love Django :)
- GEODjango contrib module. With Postgres database and PostGIS extension, it’s really easy to create applications with geolocation
- Wagtail. This CMS on top of Django is probably the best option you have now for Django. Includes beautiful extendable admin, WYSIWYG editor, page tree structure, image library, version control (revisions). Majority of functionalities for I Ride Bicycle was built on top of Wagtail’s already existing solutions. This CMS has amazing documentation and community.
- Geonames database for different locations on a map. Getting data from them could be quite a complex task but we’re lucky to have already existing package to do so :)
For the frontend, I was considering multiple options, based on Vue. The very first idea was to use Nuxt.js which is a very cool framework built on top of Vue, Vue Router, Vue SSR and which makes creating hybrid applications (frontend and serverside rendered) quite easy. Another benefit of Nuxt.js is that you create your Vue components with all styles in one codebase, without the need to switch between separate CSS files and js code. After some experiments, I decided to abandon this approach because it was adding quite a lot of complexity to my code. So the final decision was to use Django’s templates and create multiple Vue applications that could be then added to pages. Here’s the final list:
- Vue
- Mapbox-gl and Vue-mapbox — these are used to display maps, markers, routes and all the stuff. Vue-mapbox library makes it very easy to add logic and interactions to your mapbox maps.
- Webpack. It was used to solve a few tasks. First of all the most obvious one — bundling source files with all the conversion and optimization. Also because it was planned to have multiple Vue applications I used webpack to create multiple bundles. The one interesting task was to separate part of CSS that’s responsible for styles above the fold and put it into Django’s base template without manual creation of separate styles. It was successfully solved by webpack with some plugins which I will describe later.
- SCSS — i tend to create a lot of small SCSS files that are later combined into the final one.
- Bootstrap 4 — you’re smashing your monitor now probably :) Why the hell to use this library, which also heavily depends on jQuery, when you have Vue?! Well, Bootstrap 4 has very useful grid functions and responsiveness mixins in scss. Also they give you buttons, which is also very useful. If you make you main scss file include Bootstrap’s functions and mixins in a proper way, you get very useful helpers without increasing your final bundle size.