During 2017 I built a simple static website for a small business. I was recently asked to make a series of changes, and here I detail the process of implementing them.

It's worth noting I built this for my dad. I realise this particular project isn't for a real client or myself, but I thought it was worth sharing anyway.

The previous site

First of all, here's the initial site I built last year:

The old version of the site

As you can see, it's a fairly basic one-page site that covered the fundamental requirements. It was all he needed at the time.

The tech I used included Django 1.11, Python 2, Bootstrap 4 Alpha, Sass, jQuery, the Mailgun API, and PostgreSQL.

Project brief

My dad wanted to upgrade his website because he's now focusing on B4UBUY as his primary business. It's important for him to gain clients using a site that converts as effectively as possible.

To help with that, he worked with an SEO specialist (something I don't specialise in) who developed a list of requirements I was assigned to implement:

  • Add a contact page and about page
  • Add testimonials to the homepage
  • Update the site's copy (which was provided to me)
  • General SEO optimisations (adding robots.txt, sitemap.xml, <meta> tags etc.)
  • Enable HTTPS

Simple enough. I also added extra requirements for myself on top of this:

  • Improve the overall design
  • Implement smooth animations to make the site feel modern
  • Clean up the codebase
  • Add an admin panel for services and testimonials to be managed (so a developer isn't needed)
  • Implement Webpack for static asset management
  • Implement Docker for deployment and local development
  • Write unit and functional tests

Some of these requirements weren't necessary, but I chose to go ahead and incorporate them anyway. They're skills I've recently learned and wanted to practice. I still haven't implemented all of them, so this post just covers the main things.

Building the site

Setting up the project

To start off, I updated the .gitignore to a standard template for the stack using gitignore.io. The previous version was incomplete with only a few entries.

Following this, I installed Pipenv, ported the dependencies from requirements.txt, and updated to the latest versions of both Python and Django. I fixed a few minor compatibility issues in doing this.

I also setup Pylint and used it to assist in cleaning up the old, messy back-end code. My code quality has improved dramatically since I've developed an enthusiasm for the art of writing clean code and its importance.

I then span up a PostgreSQL 10.2 container with Docker instead of using a system install. It's easier to setup and has many advantages, so there's no reason I wouldn't use Docker. Later on I'll be setting up a complete stack for the application, but this does the job for now.

Following that I updated to the latest version of Bootstrap 4, since the alpha version is outdated.

Nailing the basic requirements

To get the ball rolling I nailed the easiest wins first. So the next thing I did was add the robots.txt file supplied to me using Django's TemplateView:

class RobotsTxtView(TemplateView):
    content_type = 'text/plain'
    template_name = 'main/robots.txt'

Easy. Next, I setup the sitemap with Django:

class StaticViewSitemap(Sitemap):
    changefreq = 'daily'
    lastmod = timezone.now()
    protocol = 'https'

    def items(self):
        return ['main:home_page', 'main:about_page', 'main:contact_page']

    def location(self, item):
        return reverse(item)

    def priority(self, item):
        if item == 'main:home_page':
            return 1
        return 0.5

By default example.com is used in sitemaps. To fix this, I setup Django's sites framework and a DB migration to use the correct domain:

from django.db import migrations
from django.conf import settings


def update_site_name(apps, schema_editor):
    SiteModel = apps.get_model('sites', 'Site')
    domain = 'b4ubuyhomeinspectionsadelaide.com.au'

    SiteModel.objects.update_or_create(
        pk=settings.SITE_ID,
        domain=domain,
        name=domain
    )


class Migration(migrations.Migration):
    dependencies = [
        ('main', '0003_inspectionitem'),
        ('sites', '0002_alter_domain_unique'),
    ]

    operations = [
        migrations.RunPython(update_site_name),
    ]

This is applied whenever migrations are run, ensuring consistency across all environments (including production).

Adding general SEO optimisations

This was simple so I won't get into the details, but here's what I did:

  • Added a <meta> tag for Google Site Verification that's only rendered during production
  • Updated the homepage <title> and <meta name="description">
  • Added canonical <link> tags for all pages
  • Updated each <img>'s alt attribute to be more descriptive

For the canonical <link> tag, I added it to the base template used in every page so that it's automatically generated:

<link rel="canonical" href="https://{{ domain }}{{ request.path }}" />

This was achieved using a context processor for the domain variable:

def domain(request):
    return {
        'domain': get_current_site(request).domain,
    }

Writing the unit tests

The original code didn't contain any unit tests, as TDD is something I've only recently picked up. I wrote tests for all the views, forms, and models used in the code, for things such as:

  • Testing form validation logic is working correctly
  • Asserting each view returns a HTTP 200 status code
  • Testing creating enquiries via its API endpoint
  • Ensuring the homepage renders all services and testimonials
  • Testing sending enquiries via email

This process also assisted in refactoring and cleaning up the codebase.

Updating and improving the navigation bar

Despite the current navigation bar requiring new links for the about and contact pages, I thought it was still boring and could be more useful.

So to improve it alongside the new links, I also added a phone contact button:

Navigation bar

This was a simple addition that made a significant increase to the usefulness and feel of the site.

Adding a footer

Even with the navigation bar added, the site's layout was still pretty empty. To spice it up I added a simple footer:

Footer

This is a simple change with a pretty decent impact on the design, so it was worth adding.

Prerequisites for adding new pages

The previous codebase wasn't very modular, so to add the new pages I needed to make a few changes:

  • Rewrite the homepage's enquiry form to be resuable elsewhere (in this case, the contact page)
  • Rename homepage.html to home-page.html to follow the template convention of pagename-page.html
  • Rename the homepage's static assets, views, and routes using a similar convention
  • Split the site's monolithic stylesheet homepage.sass into global and page-specific stylesheets
  • Move the navigation bar and other global HTML to the base template, so it's used in all pages

Building the contact page

Through the process of experimentation I ended up with the following design:

Contact page

When building simple layouts, I usually find experimenting with Bootstrap to be a quick and easy way to play around with ideas. It's great if you're someone like me who doesn't have UI design software, or if it's unnecessary (although I think it's very useful and is something I plan to learn).

Building the about page

The content for the "Who we are" section on the home page needed to be moved to the about page.

With some minor improvements to its formatting and layout, and an additional image for the Master Builders logo, I ended up with the following design:

About page

Updating the home page

Besides some minor changes to the page's copy, I added a new section for the testimonials. Here's the final design I came up with:

Testimonials

The testimonials are ordered by most recent, and the link to view more only shows when there's more than three. When clicked, more testimonials are smoothly displayed using an ease-in-out transition.

Adding page animations

Adding some smooth, simple animations can significantly improve the feel and user experience of a website. It's easy to implement, too.

I didn't record any footage of the animations, but you can check them out on the site.

Another thing to note is that I used CSS animations. This is important because they're very fast, whereas JavaScript animations are slow and usually have a very low FPS on mobile devices.

The first thing I added was an animation to hide the navigation bar as the user scrolls down, and reveal it as they scroll upwards again. This provides more screen real estate (which is especially useful on mobile devices), and makes the site feel more modern.

Here's the code for that:

#navbar.scroll-up {
  transform: translateY(-150px);
}
var lastScrollPosition = 0;
var currentScrollPosition = 0;
var $navbar = $('#navbar');
var $navbarContent = $navbar.find('#navbar-content');
var $navbarToggler = $navbar.find('.navbar-toggler');
var $window = $(window);

$window.scroll(function()
{
  currentScrollPosition = $window.scrollTop();

  var navbarMenuIsOpen = $navbarContent.hasClass('show');
  var navbarHeight = $navbar.height();
  var buffer = navbarHeight * 2;

  var userScrolledBehindNavbar = currentScrollPosition <= navbarHeight;
  var userScrolledDownwards = lastScrollPosition < currentScrollPosition;
  var userScrolledPastBuffer = currentScrollPosition > buffer;

  if (userScrolledDownwards && userScrolledPastBuffer && !navbarMenuIsOpen) {
    $navbar.addClass('scroll-up');
  } else if (!userScrolledDownwards && !userScrolledBehindNavbar) {
    $navbar.removeClass('scroll-up');
  }

  lastScrollPosition = currentScrollPosition;
});

Page loading animations

The second thing I added were animations that smoothly reveal the page's content once it's loaded.

Animations such as these are simple to implement, but can make a site feel significantly more interactive than a static UI.

To achieve this, I created classes using CSS animations that can be applied to any element. These classes use CSS's keyframes and animation properties:

  1. .fade-in-opacity, which smoothly fades in elements
  2. .fade-in-down, which does the same thing as the first, but also moves it downwards
  3. A series of classes for setting the animation-delay of any animation, so that some elements can animate later than others (instead of having the entire page load at once)

Adding the admin interface

The admin site that comes with Django is one of its many great built-in features. It's super easy to setup with minimal configuration, and is also mobile-friendly.

To implement it, I added the Enquiry, Service, and Testimonial models to the admin so they can be managed without a developer (me):

Admin interface home screen

Each model has its own list for its objects:

Testimonial list in admin interface

And each object has its own form where it can be edited:

Testimonial form in admin interface

You can even tweak the permissions, disabling things such as the ability to edit fields, delete objects, or add new objects. I did this for the Enquiry object, since enquiries are managed by the app itself.

Other general improvements

There were a few more improvements I made:

  • Improved the design on mobile devices
  • Implemented Icomoon for specific icons instead of using Google's Material Icons CDN for all icons

Conclusion

That's a decent overview of everything I worked on to get the site to this stage.

Besides the back-end changes you can't see, if you compare the new version to the old version, you'll see the new version is a dramatic improvement. I've compared it to the sites of competitors, and this is definitely ahead of the game.

The site is live and working, but as mentioned I'll be adding some more upgrades to practice my skill-set.