How I Built My Website In React and Firebase

A web-design colleague once said that your personal website is like cobblers’ shoes. You’re always too busy making things for other people to do a nice job for yourself. I’ve always found my personal website a challenge to design effectively but this time I had something in mind that I thought could work well and look really good.

The Old Design
michael forrest site app academy
Previous version of michaelforrestmusic.com.

My old site had manually created content and pages mixed in with embeds from other platforms. The primary content was slow to update (I was using Jekyll so I had to make a new markdown file with the right naming convention, write a post and then deploy the site — just enough friction to make it feel like a chore). The site embeds might have had a bit more life but they weren’t really the focus. I had fallen into a habit of just posting up my YouTube videos with no commentary whatsoever because I was never in a prose writer’s mindset when putting things on this page.

New Design

I have a lot of content on a lot of different platforms: YouTube, SoundCloud, Bandcamp, Instagram and others. I tend to go through phases with these things — earlier this year I was working on YouTube videos, then I was writing a lot of blog posts on Medium, and lately I’ve been making a lot of content on Instagram.

For the new design I wanted to bring all my content into one place and mix it with my manually created content. I was feeling confident enough in my frontend dev skills to attempt a responsive grid-based layout with everything in one big scrolling page. The tiles would have different sizes according to their importance relative to each other piece of content rather than being organized by platform.

I didn’t really open up Sketch to do any visual design until I was well into development. I focused on solving technical problems one at a time until the visual layout started to need some love. I wanted to test the technical constraints first.

Build Environment: Create React App

I used Create React App to kick off my build. I’d used it in a recent high-pressure project for a client so I felt comfortable with it.

Redux

I’m using Redux to manage my data access. You can see what’s happening as the page builds if you install Redux Dev Tools.

Redux Dev Tools

It takes a while to get your head around and there’s a lot of boilerplate to get it up and running but it’s really nice to be able to explore data structures without having to add breakpoints or console logs, so it’s worth it.

Data Source: Firebase

I wanted to ingest content from different platforms but I didn’t want to do that every time for every user. I decided to use the Firebase data store as a stable caching layer between my frontend and those other platforms to minimise the number of moving parts for the public website.

I’d recently built myself a Chrome extension that harvested data from all the different APIs I was interested in, so I’d already done a lot of the groundwork for this.

Exploring APIs and their different approaches to authentication etc… on different platforms. Insomnia was an invaluable tool for keeping all my findings together
michael forrest site coding in react
My personal Chrome extension — shows me my latest counts and populates the database for my website every time I open a new tab.
Content Management

The basic process is that I upload content to Instagram or wherever, and then next time I open a new Chrome tab, it gets stored in Firebase and pushed to the website.

I add additional metadata to some items through the Firebase web interface if I need to. This will be merged with the latest content from the other platforms each time I open a new tab so I only add, I don’t modify any ingested data.

michael forrest react code app academy
Firebase interface with added fields like `shrunk` on a YouTube video to show it smaller, or `hasDisappeared` on a release to change how it is displayed in the app.
Grid Layout

I hoped to find something like Masonry to manage the layout automatically. I ended up settling on the React Grid Layout library but soon found that I would have to provide my own layout code. The framework provided different breakpoints with different numbers of columns for different widths so I just had to say, for each size, where I wanted each tile to live on the grid and how big it should be. This was good because I could make my own decisions about some of the subtleties. For example, tiles are fixed to multiples of the row height and I have lots of single-square tiles to fill in any gaps so it generally looks nice and full.

Here’s what my layout code looks like, if you’re interested.

const squareSize = (maxColumns)=>{
  if(maxColumns > 8){
    return {w: 4, h: 4}
  }
  return {w: 3, h: 3}
}
const landscapeSize = (maxColumns)=>{
  if(maxColumns > 8){
    return {w: 5, h: 3}
  }
  return {w: 3, h: 2}
}
const postSize = (post,numColumns)=> {
  switch (post.serviceName) {
    case 'youtube': return post.shrunk ? {w: 1, h: 1} : landscapeSize(numColumns)
    case 'instagram': return {w:1, h: 1}
    case 'releases': return squareSize(numColumns)
    case 'soundcloud': return {w:1, h:1}
    case 'medium': return post.virtuals.previewImage.imageId ? {w: 2, h: 2} : {w: 1, h: 1}
    case 'legacy': return {w: 2, h: 2}
    default: return {w: 2, h: 1}
  }
}
const areaIsFree = ({rows, x, y, w, h})=> {
  for(var row = y; row < y + h; row ++){
    if(row < rows.length && x + w - 1 >= rows[row].length) return false
    for(var col = x; col < x + w; col ++ ){
      if(row < rows.length && rows[row][col]) return false
    }
  }
  return true
}
const nextFreeSlot = (rows,{w,h})=>{
  for (var rowIndex = 0; rowIndex < rows.length; rowIndex++) {
    const row = rows[rowIndex]
    for (var columnIndex = 0; columnIndex < row.length; columnIndex++) {
      if(areaIsFree({rows, x: columnIndex, y: rowIndex, w, h})){
        return {x: columnIndex, y: rowIndex}
      }
    }
  }
  return {x: 0, y: rows.length}
}
const createRow = (numColumns)=> new Array(numColumns).fill(0)
export const mapPostsToLayout = (posts,numColumns) => {
  var slots = [createRow(numColumns)]
  return posts.map((post,index)=>{
    const {w,h} = postSize(post,numColumns)
    const {x,y} = nextFreeSlot(slots, {w,h})
    for(var row = y; row < y + h; row ++){
      for(var col = x; col < x + w; col ++ ){
        while(row >= slots.length) slots.push(createRow(numColumns))
        slots[row][col] = 1
      }
    }
    return {
      i: `${post.id}`,
      x,y,w,h
    }
  })
}
Grid layout at different widths.
Detail pages

When you click a link you see a content preview (or sometimes all the content). I wanted you to be able to step through content without returning to the grid page every time so I needed next/previous buttons.

Overlays felt right for desktop but for mobile it became pretty cramped so I take over the whole page if you’re on a narrow device. You can see the mechanism at work if you rotate your phone when you’re looking at a detail page — in portrait you’ll see the content full screen as a scrolling page, but if you rotate your device you will see it as an overlay as the width is now larger than the expected portrait-oriented mobile bounds.

 
Detail pages on Desktop vs. Mobile
Detail pages on Desktop vs. Mobile
 
Social media links and email capture

The sad fact of being an independent artist is that you need social media followers and lots of them if you’re ever going to be taken seriously by the industry at large. I wanted to make sure my other platform links were always available, so they live in a fixed header.

However, I’m always hearing that the most important thing is to have an email list. Personally I have never sent out a group email. This means most people miss my stuff as it disappears forever off the bottom of their Facebook feeds, if it ever gets shown at all. I decided to make a concerted effort to start catching emails with the new site and took some cues from Tim Exile on this subject.

My footer is hot pink and it shows up every time you visit the site (I decided not to save whether the user had given their email in a cookie to avoid having to add a cookie warning which would have been just as intrusive). I even went the extra mile and made it say thank you when you come back from entering your email.

The Details

I finally jumped into Sketch and tidied up and tinkered with some graphic design ideas. I just stacked in screenshots and added overlays to see how different ideas would look in situ. As I was building the site I didn’t have to make these super-detailed and I could change the look based on what the technology made easy when it came to putting it together.

Main page design — basically some stacked screen grabs with modifications made in place
 
Hosting

The site is hosted on Amazon S3. I just use yarn build and then push the build folder to my configured S3 bucket. There is no server-side code. All CMS-type logic is done in separately in my Chrome extension.

Conclusion

This build came together to a higher standard and with less friction than anything I’ve ever built for myself before. Being able to work entirely client-side was a large part of that. I was able to apply some of my mobile app development experience to optimise behaviour on a level that isn’t possible with plain HTML. Create React App does an excellent job of hiding all the complexities of webpack, babel et al, so I can just write my app without having to maintain a lot of complex boilerplate to make it build and automatically reload and everything else that you want to have in a modern build process.

My SEO will be worse with this site than it was on my previous site. I’m prepared to live with this for now.

I had plans for filtering and some extra content pages but I’ll add these in future. For now I think it works well and I hope it will lead to more people discovering all the things I’ve made.

The site is live now at michaelforrestmusic.com

What do you think?

19 points
Upvote Downvote

Leave a Reply