How to set up a personal website with 11ty, Markdown, TailwindCSS, and Alpine.js
Maciej
Posted on August 23, 2021
So I rebuild my website for about 100th time.
I was testing out some new website stacks, and this is the one I particularly enjoy.
We will use:
- 11ty static site generator
- markdown files for content such as blog posts and projects
- Tailwind to add some styling
- Alpine.js for some interactivity, e.g mobile menu
- Netlify to host our website
When finished you will have a website that you can use as your personal developer portfolio.
Show it to your employers or clients to showcase your work and establish expertise.
This tutorial assumes you have basic understanding of Tailwind, HTML, JavaScript, and npm.
Getting Started
Create a new directory for our project, and initialize it with package.json
npm init -y
Now let's install eleventy, tailwind, and posts
npm install --save-dev @11ty/eleventy tailwindcss postcss-cli autoprefixer
Now let's create a index.liquid file (liquid is one of the templating languages supported by 11ty )
---
title: "Home Page"
description: "This is our homepage"
layout: layouts/main
---
<h1>{{ title }}</h1>
<p>{{ description }}</p>
The code between --- signs is the front matter.
Here we define variables for different pages. We can access them in our html using double curly brackets (if you used Django this should be familiar to you)
Layout variable is to point 11ty to a file that our page will use.
This way we can reuse the same code across many pages with out writing it more than once.
Templating
Let's create a directory called _includes
, this is where we can create reusable blocks of code for layouts, and components.
Let's create another directory called layouts
and inside a file for our HTML boilerplate main.liquid
---
title: Default Title
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
{% if description %}
<meta name="description" content="{{description}}" />
{% endif %}
<link rel="stylesheet" href="/style.css?v={% version %}" />
</head>
<body>
<div id="content" class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="max-w-3xl mx-auto">
{% block content %}
{{ content | safe }}
{% endblock %}
</div>
</div>
</body>
</html>
TailwindCSS Config
Create a folder called styles
and a file inside tailwind.config.js
module.exports = {
purge: [
'_site/**/*.html',
],
darkMode: false,
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [],
}
Create another file in styles
folder called tailwind.css
@tailwind base;
@tailwind components;
@tailwind utilities;
In the root directory of the project create a posts.config.js
file
module.exports = {
plugins: [
require(`tailwindcss`)(`./styles/tailwind.config.js`),
require(`autoprefixer`),
],
}
Setting up the project
Create a .gitignore
file in root directory
_site/
_tmp/
.DS_Store
node_modules/
package-lock.json
Next create a .eleventyignore
file
node_modules
Finally let's create 11ty configuration file .eleventy.js
const now = String(Date.now())
module.exports = function (eleventyConfig) {
eleventyConfig.setUseGitIgnore(false)
eleventyConfig.addWatchTarget('./_tmp/style.css')
eleventyConfig.addPassthroughCopy({ './_tmp/style.css': './style.css' })
eleventyConfig.addShortcode('version', function () {
return now
})
};
Now update package.json
file with scripts to start the build.
{
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "eleventy --serve & postcss styles/tailwind.css --o _tmp/style.css --watch",
"build": "ELEVENTY_PRODUCTION=true eleventy & NODE_ENV=production postcss styles/tailwind.css --o _site/style.css"
},
"devDependencies": {
"@11ty/eleventy": "^0.12.1",
"autoprefixer": "^10.3.2",
"postcss-cli": "^8.3.1",
"tailwindcss": "^2.2.7"
}
}
Now we can build the project
npm run build
And to start the site
npm run start
Open http://localhost:8080
in your browser and you should see this in your browser
Optimising HTML
Now we want to improve our site by minifing HTML and cleaning CSS. Install required packages
npm install --save-dev html-minifier clean-css-cli
Next update .eleventy.js
const htmlmin = require('html-minifier')
const now = String(Date.now())
module.exports = function (eleventyConfig) {
eleventyConfig.setUseGitIgnore(false)
eleventyConfig.addWatchTarget('./_tmp/style.css')
eleventyConfig.addPassthroughCopy({ './_tmp/style.css': './style.css' })
eleventyConfig.addShortcode('version', function () {
return now
})
eleventyConfig.addTransform('htmlmin', function (content, outputPath) {
if (
process.env.ELEVENTY_PRODUCTION &&
outputPath &&
outputPath.endsWith('.html')
) {
let minified = htmlmin.minify(content, {
useShortDoctype: true,
removeComments: true,
collapseWhitespace: true,
});
return minified
}
return content
})
}
Lastly update the build script in package.json
{
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "eleventy --serve & postcss styles/tailwind.css --o _tmp/style.css --watch",
"build": "ELEVENTY_PRODUCTION=true eleventy && NODE_ENV=production postcss styles/tailwind.css --o _site/style.css && cleancss _site/style.css -o _site/style.css"
},
"devDependencies": {
"@11ty/eleventy": "^0.12.1",
"autoprefixer": "^10.3.2",
"clean-css-cli": "^5.3.3",
"html-minifier": "^4.0.0",
"postcss-cli": "^8.3.1",
"tailwindcss": "^2.2.7"
}
}
Run npm run build
again and you should see your files in _site
directory take less space
Adding Alpine.js
Now for the last bit of configuration let's add Alpine.js.
First install it through npm
npm install --save-dev alpinejs
Update the .eleventy.js
file
eleventyConfig.addPassthroughCopy({
'./node_modules/alpinejs/dist/cdn.js': './js/alpine.js',
})
Finally update the _includes/layouts/main.liquid
, by importing alpine in the <head>
<script src="/js/alpine.js?v={% version %}"></script>
Now let's complete the website by adding pages, blog pages and styling it
Adding hero header
Inside _includes
dir create a components
folder with hero.liquid
file inside.
<div class="relative">
<div class="absolute inset-0">
<img class="w-full h-full object-cover"
src="https://images.unsplash.com/34/BA1yLjNnQCI1yisIZGEi_2013-07-16_1922_IMG_9873.jpg?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1502&q=80"
alt="">
<div class="absolute inset-0 bg-gray-400 mix-blend-multiply" aria-hidden="true"></div>
</div>
<div class="relative text-center py-24 px-4 sm:py-32 sm:px-6 lg:px-8">
<h1 class="text-5xl font-extrabold text-white sm:text-6xl lg:text-7xl">My Personal Website</h1>
<p class="mt-4 text-xl max-w-3xl">Read my web development tutorials, and see projects I worked on.
</p>
</div>
</div>
Now update the index.liquid
file by including the header
---
title: Home Page
description: This is our homepage
layout: layouts/main
---
{% include components/hero %}
Run the site npm run start
and you should see our hero header
Adding a Navigation Bar
Create a navigation.liquid
file in the _includes/components
directory
<div class="mt-6 flex justify-between items-center">
<a class="font-extrabold uppercase text-xl" href="/">Web Dev Blog</a>
<button @click="isOpen = !isOpen" class="block md:hidden p-1 rounded-lg bg-gray-900 bg-opacity-80 hover:bg-opacity-100 text-gray-200">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
<div class="hidden md:flex md:justify-end md:items-center md:space-x-6">
<a class="font-semibold uppercase hover:opacity-80 hover:underline" href="/">Home</a>
<a class="font-semibold uppercase hover:opacity-80 hover:underline" href="/blog">Blog</a>
<a class="font-semibold uppercase hover:opacity-80 hover:underline" href="/about">About</a>
<a class="font-semibold uppercase hover:opacity-80 hover:underline" href="/work">Work</a>
</div>
</div>
Making a Mobile Nav
Now let's make a mobile navigation with the help of some Alpine.js magic
<style>
[x-cloak] {
display: none;
}
</style>
<div x-cloak x-data="navigation()">
<!-- Mobile navigation -->
<div class="-mt-6 absolute z-50 bg-white h-screen w-screen" x-show="isOpen"
x-transition:enter="transition ease-in-out duration-300"
x-transition:enter-start="opacity-0 transform scale-x-0 -translate-x-1/2"
x-transition:enter-end="opacity-100 transform scale-x-100 translate-x-0"
x-transition:leave="transition ease-in-out duration-300"
x-transition:leave-start="opacity-100 transform scale-x-100 translate-x-0"
x-transition:leave-end="opacity-0 transform scale-x-0 -translate-x-1/2">
<div class="flex justify-between items-center p-6">
<a class="font-extrabold uppercase text-xl" href="/">Web Dev Blog</a>
<button @click="isOpen = !isOpen" class="block md:hidden p-1 rounded-lg bg-gray-900 bg-opacity-80 hover:bg-opacity-100 text-gray-200">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="grid grid-cols-1 gap-4 p-6">
<a class="font-semibold uppercase hover:opacity-80 hover:underline" href="/">Home</a>
<a class="font-semibold uppercase hover:opacity-80 hover:underline" href="/blog">Blog</a>
<a class="font-semibold uppercase hover:opacity-80 hover:underline" href="/about">About</a>
<a class="font-semibold uppercase hover:opacity-80 hover:underline" href="/work">Work</a>
</div>
</div>
<!-- Desktop navigation -->
<div class="mt-6 flex justify-between items-center">
<a class="font-extrabold uppercase text-xl" href="/">Web Dev Blog</a>
<button @click="isOpen = !isOpen" class="block md:hidden p-1 rounded-lg bg-gray-900 bg-opacity-80 hover:bg-opacity-100 text-gray-200">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
<div class="hidden md:flex md:justify-end md:items-center md:space-x-6">
<a class="font-semibold uppercase hover:opacity-80 hover:underline" href="/">Home</a>
<a class="font-semibold uppercase hover:opacity-80 hover:underline" href="/blog">Blog</a>
<a class="font-semibold uppercase hover:opacity-80 hover:underline" href="/about">About</a>
<a class="font-semibold uppercase hover:opacity-80 hover:underline" href="/work">Work</a>
</div>
</div>
</div>
<script>
function navigation() {
return {
isOpen: false
}
}
</script>
Now let's include the navigation in our layout file _includes/layouts/main.liquid
<body>
<div id="content" class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="max-w-3xl mx-auto">
{% include components/navigation %}
{% block content %}
{{ content | safe }}
{% endblock %}
</div>
</div>
</body>
Preview our website in the browser
Making reusable components
Let's make our hero header reusable by adding few variables to it, for title text, sub text and image url. Update _includes/compnoents/hero.liquid
<div class="mt-2 relative">
<div class="absolute inset-0">
<img class="w-full h-full object-cover"
src="{{ hero_img }}"
alt="">
<div class="absolute inset-0 bg-gray-400 mix-blend-multiply" aria-hidden="true"></div>
</div>
<div class="relative text-center py-24 px-4 sm:py-32 sm:px-6 lg:px-8">
<h1 class="text-5xl font-extrabold text-white sm:text-6xl lg:text-7xl">{{ hero_title }}</h1>
<p class="mt-4 text-xl text-gray-50 max-w-3xl">{{ hero_subtitle }}</p>
</div>
</div>
Next update the include in the index.liquid
---
title: Home Page
description: This is our homepage
layout: layouts/main
---
{% include components/hero, hero_title: "My Web Development Blog", hero_subtitle: "Read my web development tutorials,
and see projects I worked
on.", hero_img:
"https://images.unsplash.com/34/BA1yLjNnQCI1yisIZGEi_2013-07-16_1922_IMG_9873.jpg"
%}
Check if everything still work in the browser.
Adding pages
Let's create an about page. In the root directory create a file about.liquid
---
title: About Page
description: This is the about page
layout: layouts/main
---
{% include components/hero, hero_title: "About Me", hero_subtitle: "Learn more about me here", hero_img:
"https://images.unsplash.com/photo-1628373791626-fe21d99fbd58?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1500&q=80"
%}
<p>Here is a short summary about me and what I do.</p>
Visit http://localhost:8080/aboutt
in your browser
Creating collections
Create a new directory in the root dir of the project called blog
Inside create 3 files blog1.md
, blog2.md
,index.liquid
The markdown files will be used to render our blog articles content, and the index.liquid will list all the articles under /blog
blog1.md
---
title: Example Blog post no. 1
description: This is a basic description of the post
date: 2021-08-21
layout: layouts/blog
tags: blog
---
This is some placeholder content that will be rendered to html
blog2.md
---
title: Example Blog post no. 1
description: This is a basic description of the post
date: 2021-08-23
layout: layouts/blog
tags: blog
---
This is another peace of placeholder content that will be rendered to html
blog/index.liquid
---
title: About Page
description: This is the about page
layout: layouts/main
---
{% include components/hero, hero_title: "Blog", hero_subtitle: "Read my articles", hero_img:
"https://images.unsplash.com/photo-1628607292260-9195108b03b7?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1502&q=80"
%}
<div class="mt-6 grid grid-cols-1 md:grid-cols-2 gap-4 ">
{% for article in collections.blog reversed %}
<a href="{{ article.url }}" class="p-4 border rounded shadow hover:bg-gray-100">
<h3 class="text-lg font-bold">{{ article.data.title }}</h3>
<p class="text-gray-700">{{ article.data.description }}</p>
</a>
{% endfor %}
</div>
Finally we need to create a layout for articles, create _includes/layouts/blog.liquid
file
---
layout: layouts/main
---
{% include components/hero, hero_title: title, hero_subtitle: description, hero_img:
"https://images.unsplash.com/photo-1628366757132-6c49770ec9d7"
%}
<div class="mt-6 md:mt-12">
{{ content | safe }}
</div>
Now visit http://localhost:8080/blog
to view a list of posts, and click one of them to see the content
You can do the same to list projects you worked on, but I will let you add it yourself as an exercise.
Deploying website to Netlify
Firstly, let's upload our website to GitHub.
Create a new repo on GitHub, and then initialise git local and upload our files
git init
git add .
git commit -m "first commit"
git branch -M main
git remote add origin https://github/your_username/your_repo.git
git push -u origin main
Now go to Netlify.com, login or register if you don't have an account.
On main dashboard click New site from Git
button. And under Continuous Deployment
click GitHub
button. Authenticate your GitHub account, and you should see a list of your repositories.
Select the one we just created.
On the next screen leave everything on defaults, and press Deploy
.
You built and deployed your eleventy blog to netlify and should have your website available on address like that.
The website will automaticly deploy when you push changes to github.
Here is a link to GitHub repo of the finished project.
If you liked this guide, follow me on twitter, to stay up to date with my latest content.
Posted on August 23, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
August 23, 2021