The RikVerse rebuild: Setting up the shiny

tl;dr: Rik begins building his new poetry website using all the goodness of Svelte, Page.js and Tailwind - with added font fun

This is the second in a series of blog posts detailing my journey to rebuild my poetry website - The RikVerse - using Svelte.js, Page.js and Tailwind CSS. Other posts in this series:

  1. Introduction to the project
  2. Setting up the shiny
  3. The client is not the server - 404 errors
  4. Building the blog reader
  5. Fun with Cookies!
  6. The book store bits
  7. Sharing is Caring - social media share buttons
  8. … And finally: the poems!

The code developed in this blogpost can be found here on GitHub.

Where were we?

In the previous post I reviewed my current poetry website, determined twelve goals for rebuilding it, and made some decisions concerning the tools I would use for the rebuild. As a reminder, my twelve goals are:

  • Simpler, more minimal design; better fonts
  • The landing page should display a randomly selected poem
  • Navigation needs to be massively simplified
  • Add in cookie consent (meeting UK ICO guidance for explicit opt-in)
  • Get rid of the database!
  • Index of all the poems available to read
  • Tag filtering functionality on the poems index page
  • Easy for the user to access a poem’s associated media files (images, audio, video)
  • Make each publication page that book’s keystone page
  • Let people read the books in-site
  • Keep the donate button; make it More Fun To Use
  • Add in poetry-and-writing-related blog posts

And the tools I’m going to use to build the site are:

My inspiration for choosing these tools was a blog post by Jack Whiting, where he detailed his experience of setting up a new project using them.

So … time to start the new build!

Initial work - Svelte

For the setup, I followed the processes set out by Mr Whiting quite closely. I opened up my trusty terminal, navigated to my personal coding directory and started issuing the relevant commands:



1
2
3
4
$> npx degit sveltejs/template rikverse2020
$> cd rikverse2020
$> yarn install
$> yarn dev

… It really is that simple! I navigated to localhost and admired Svelte’s clever work:

There’s three files that drive this magic: ./public/index.html, ./src/App.svelte, and ./src/main.js

./public/index.html is the only file which makes it to the user’s browser unchanged:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1'>

<title>Svelte app</title>

<link rel='icon' type='image/png' href='/favicon.png'>
<link rel='stylesheet' href='/global.css'>
<link rel='stylesheet' href='/build/bundle.css'>

<script defer src='/build/bundle.js'></script>
</head>

<body>
</body>
</html>

This is because Svelte isn’t a client-side framework (like React or Vue). Instead Svelte builds your site for you, converting all your pages and components (and their associated styling and Javascript functionalities) into two files - bundle.js and bundle.css - which it then deposits into the ./public/build folder ready for us to deploy to wherever.

./src/App.svelte - is the root file which tells Svelte which components/pages need to appear in the browser. Currently, it looks a bit sad - but that will all change as we progress:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<style>
main {
text-align: center;
padding: 1em;
max-width: 240px;
margin: 0 auto;
}
h1 {
color: #ff3e00;
text-transform: uppercase;
font-size: 4em;
font-weight: 100;
}
@media (min-width: 640px) {
main {
max-width: none;
}
}
</style>

<script>
export let name;
</script>

<main>
<h1>Hello {name}!</h1>
<p>Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.</p>
</main>

./src/main.js connects App.svelte to index.html:

1
2
3
4
5
6
7
8
9
10
import App from './App.svelte';

const app = new App({
target: document.body,
props: {
name: 'world'
}
});

export default app;

Svelte comes with a pretty good tutorial, and its documentation is not too bad either!

Styling Svelte using Tailwind

Next, I added Tailwind CSS and PostCSS to the mix:

1
2
3
4
5
6
$> yarn add --dev postcss postcss-load-config svelte-preprocess tailwindcss
$> yarn add @fullhuman/postcss-purgecss

$> npx tailwind init

$> touch postcss.config.js

At this point Mr Whiting told me I to go in and edit various files - ./postcss.config.js, ./src/App.svelte, and ./rollup.config.js … of course I did as I was told, following his instructions to the letter.

With the files edited and saved, I checked my work:

1
$> yarn dev


… I haven’t broken anything yet!

Fun with fonts!

In 2012 I made a decision to make my poetry site entirely Serif (Merriweather), with monospace (LiberationMono) for the more space-y poems. I can’t remember which cursive font I used for the pretty banner and navigation labels, but they were images so it doesn’t matter.

Merriweather was a bad choice. When bolded, the font lost one of the dots in its :colon. I knew I needed to make some better font choices this time around.

A bit of research suggested that one of the favourite fonts in 2019 had been Roboto. I checked it out. It did indeed look nice (for a sans-serif). It also came with a serif version - Roboto Slab - which looked equally servicable.

As Google fonts, I could add them to my site using a link element in the header of ./public/index.html - <link href="https://fonts.googleapis.com/css?family=Roboto+Slab&display=swap" rel="stylesheet">. This is what I had done with the old site’s fonts.

The big drawback to this approach is, of course, tunnels. If a user arrives on my site ready to read a poem just before their train goes into a tunnel, Google may not send the font to them in time, which leads to them having to read my poem in whatever horrid fallback font their browser or device prefers.

This is Not Good Enough!

Instead, I wanted my fonts served from a traditional ./public/fonts directory, with normal @font-face rules defined in the CSS. This meant downloading a zip package from Google Fonts … or maybe not! This is 2020, after all, and NPM exists to make installing stuff a lot easier!

There’s a lot of font installers available on NPM. I chose to go with Kyle Mathews’s typefaces package:

1
$> yarn add typeface-roboto typeface-roboto-slab

… And then the fun started.

As Mr Mathews points out: “Typeface assumes you’re using webpack with loaders setup for loading css and font files”. Except Svelte uses Rollup, and I was not about to spend an unknowable number of days investigating how to adapt Rollup to require("typeface-roboto").

I looked to see what the typeface import had deposited in my ./.node-modules/ directory.

I knew I was not going to be changing my fonts; building fonts into the site was going to be a one-time job. So I created a new directory - ./public/fonts - and copied all the font files into it. Then I manually copied over the CSS @font-face declarations into the ./public/global.css file (remembering to correct the paths to my new fonts directory).

The last job was to make Tailwind aware that I wanted to use the Roboto fonts. Tailwind’s documentation is entirely useful and informative (thank you, Tailwind folks!) so I was soon able to update the font settings in the ./tailwind.config.js file:

1
2
3
4
5
6
7
8
9
10
11
module.exports = {
theme: {
fontFamily: {
'sans': ['Roboto', 'Arial', 'sans-serif'],
'serif': ['Roboto Slab', 'Georgia', 'Cambria', 'serif']
},
extend: {}
},
variants: {},
plugins: []
}

It was also time to make some decisions about the new site’s design. Simple. Minimal. Uncluttered. Cool palette, etc.

Tailwind supplies a pre-set palette of colours which can be tweaked and tailored, if needed, in the config file. I felt no need to tweak colors; I just chose some appropriate ones.

I set out my global styling requirements in ./src/App.svelte:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<style global>
@tailwind base;

body {
@apply font-sans bg-blue-100 p-0;
font-size: 14px;
line-height: 1.25;
}
main {
@apply mx-auto px-6 pb-2 text-gray-700 bg-blue-100;
max-width: 680px;
min-height: 67vh;
}

h1, h2, h3 {
@apply font-serif font-semibold mb-4 pt-4;
}
h1 {
@apply text-3xl;
}
h2 {
@apply text-2xl;
}
h3 {
@apply text-xl;
}

p {
@apply mb-4;
}
a {
@apply text-green-700;
}

@media (min-width: 768px) {
body {
@apply bg-indigo-100 p-4;
font-size: 16px;
}
main {
@apply rounded-lg;
max-width: 760px;
}
}
@media (min-width: 1024px) {
body {
font-size: 18px;
}
main {
max-width: 920px;
}
}

@tailwind components;
@tailwind utilities;
</style>

… The time had come for me to move onto routing, and building some Svelte pages and components!

Client-side routing with Page.js

After my adventures with fonts and styling, I returned to Mr Whiting’s blog post series to learn all about Page.js routing. I followed the instructions, fixing things relevant to my site’s requirements as I went. I also refactored much of my router code into a dedicated ./src/routes.js file, which I could then import into ./src/App.svelte where all the heavy lifting would happen.

Building the router meant having to take decisions about site navigation. The old site suffered from excessive navigation; I decided to go much simpler this time. As a result, the ./src/routes.js file ended up looking like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import router from "page";

const startRouter = router.start;

import Home from "./pages/Home.svelte";
import About from "./pages/About.svelte";
import Blog from "./pages/Blog.svelte";
import PoemsIndex from "./pages/PoemsIndex.svelte";
import Publications from "./pages/Publications.svelte";
import CookieConsents from "./pages/CookieConsents.svelte";
import Privacy from "./pages/Privacy.svelte";
import ErrorPage from "./pages/ErrorPage.svelte";

const routes = [
{
// The landing page, which will show a random poem to the user
path: '/',
component: Home
}, {
// The Author page - because "Every ass loves to hear himself bray"
path: '/about',
component: About
}, {
// Index of links to individual blog posts
path: '/blog',
component: Blog
}, {
// Index of links to each of Rik's books
path: '/publications',
component: Publications
}, {
// Index of links to individual poems
path: '/index',
component: PoemsIndex
}, {
// A dedicated cookie conbsents page, where users can make appropriate choices
path: '/cookies',
component: CookieConsents
}, {
// A dedicated privacy/security page, because every website needs one
path: '/privacy-and-security',
component: Privacy
}, {
// The catch-all route, to serve an error page
path: '*',
component: ErrorPage
}
];

export {
router,
startRouter,
routes,
};

The ./src/routes.js functionality gets imported into ./src/App.svelte and used like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<style>
[see above]
</style>

<script>
import {router, startRouter, routes } from './routes.js';

import Navigation from './components/Navigation.svelte';
import Footer from './components/Footer.svelte';

let page, params;

// Build routes
routes.forEach(route => {

router(
route.path,

(ctx, next) => {
params = ctx.params;
next();
},

() => page = route.component
);
});

// Set up the router to start and actively watch for changes
startRouter();
</script>

<Navigation />

<main>
<svelte:component this={page} params={params} />
</main>

<Footer />

Result:

I made a decision to divide my Svelte files between two directories - ./src/components and ./src/pages - for nothing more than pragmatic reasons: pages relate to routes, components just show up when included wherever.

The Svelte files themselves were entirely unsophisticated; all I was interested in was making sure the links worked as advertised (they do!)

./src/pages/CookieConsents.svelte

1
2
3
4
5
6
7
8
9
10
<script></script>

<style></style>

<h3>This will be the cookie consents page</h3>

<p>
It is on this page that the user will find a link to the
<a href="/privacy-and-security">privacy and security page</a>.
</p>

./src/components/Navigation.svelte

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script></script>

<style></style>

<h3>This is the navigation component</h3>

<p>
<a href="/">Landing</a> ~
<a href="/index">Poems</a> ~
<a href="/publications">Books</a> ~
<a href="/blog">Blog</a> ~
<a href="/about">About Rik</a> ~
<a href="/cookies">Cookies</a>
</p>

But does it really, really, really work?

Have you heard the saying “Well, it works on MY machine”?

Yeah - I have the tee-shirt. And a mug with the slogan in big letters written across it.

Svelte comes with a couple of terminal commands - yarn dev and yarn build. I decided to try out the build command to see what the site would look like in a more production-y environment:

“HTTP error 404” … oops?

The thing I had forgotten was that Page.js is a client-side routing solution.

If someone goes to any route on the site that is not the landing page, and clicks on their browser’s reload button, the browser is going to ask the remote server for the page at that route.

But that page doesn’t exist on the server. It only exists in Svelte’s Javascript bundle supplied to the ./public/index.html file.

… Oops indeed! Fixing this issue is gonna need a whole new blog post!