Create a Standout Developer Portfolio: Build Your Personal Brand with HTML, Tailwind, Hashnode, and Vercel.
The job market is tough, and if you are looking for opportunities, you need to stand out. A good way to do that, is by building a developer portfolio. It can not only help you have a proof of work, but also help create your personal brand.
Even if we all know the importance of having a portfolio, we often end up not building at all, because of contemplation or wanting to over-complicate it.
In this article, I will show you how you can build a simple yet effective portfolio using:
HTML
Tailwind CSS
Hashnode and
Vercel for deployment
You can either follow this guide or watch me build it live here:
Getting started
The first step in building a portfolio or any project is to have a clear design or structure in mind. For a developer portfolio, we need to include a homepage that introduces us and highlights our skills, a section showcasing our projects, a list of articles that we have written, and links to our social media profiles.
Here's how I envisioned it to be 👇
Let us divide it into three smaller tasks:
Building the homepage that includes our bio, and projects.
Using Hashnode to build our blog section
Deploying it using Vercel.
Part 1: Building the homepage
To get started, make sure you have a repository setup on GitHub, and have cloned it on an IDE of your choice.
The next step is to install Tailwind:
On your terminal type
npm install -D tailwindcss
followed bynpx tailwindcss init
.Add the paths to all of your template files in your
tailwind.config.js
file./** @type {import('tailwindcss').Config} */ module.exports = { content: ["./src/**/*.{html,js}"], theme: { extend: {}, }, plugins: [], }
Add the
@tailwind
directives for each of Tailwind’s layers to your main CSS file.@tailwind base; @tailwind components; @tailwind utilities;
Run the CLI tool to scan your template files for classes and build your CSS
npx tailwindcss -i ./src/input.css -o ./src/output.css --watch
On your
index.html
file, add<link href="./output.css" rel="stylesheet"
\>
For this demo, we will be using Flowbite components to design the portfolio.
Here are the commands to get started:
npm install flowbite
On you
tailwind.config.js
file addflowbite
as the plugin:module.exports = { plugins: [ require('flowbite/plugin') ] }
Before the end of the body tag, add the JavaScript code that powers the interactive elements:
<script src="../path/to/flowbite/dist/flowbite.min.js"></script>
Building the header and the bio section:
<nav class="bg-white border-gray-200 dark:bg-gray-900">
<div class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4">
<a href="https://www.linkedin.com/in/haimantika-mitra/" class="flex items-center space-x-3 rtl:space-x-reverse">
<img class="w-10 h-10 rounded-full" src="img/hm.jpg" alt="Rounded avatar">
<span class="self-center text-2xl font-semibold whitespace-nowrap dark:text-white">Haimantika Mitra</span>
</a>
<button data-collapse-toggle="navbar-default" type="button" class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600" aria-controls="navbar-default" aria-expanded="false">
<span class="sr-only">Open main menu</span>
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 17 14">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 1h15M1 7h15M1 13h15"/>
</svg>
</button>
<div class="hidden w-full md:block md:w-auto" id="navbar-default">
<ul class="font-medium flex flex-col p-4 md:p-0 mt-4 border border-gray-100 rounded-lg bg-gray-50 md:flex-row md:space-x-8 rtl:space-x-reverse md:mt-0 md:border-0 md:bg-white dark:bg-gray-800 md:dark:bg-gray-900 dark:border-gray-700">
<li>
<a href="#home" class="block py-2 px-3 text-white bg-blue-700 rounded md:bg-transparent md:text-blue-700 md:p-0 dark:text-white md:dark:text-blue-500" aria-current="page">Home</a>
</li>
<li>
<a href="https://starter-kit-ophg-git-new-portfolio-haimantikas-projects.vercel.app/blog" class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Blog</a>
</li>
<li>
<a href="#projects" class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Projects</a>
</li>
</ul>
</div>
</div>
</nav>
<main class="max-w-4xl mx-auto flex flex-col">
<section id="home" class="flex flex-col sm:flex-row p-20 items-center gap-8 mb-12">
<h1 class="mb-4 text-3xl font-extrabold text-gray-900 dark:text-white md:text-5xl lg:text-6xl"><span class="text-transparent bg-clip-text bg-gradient-to-r to-emerald-600 from-sky-400">Generalist & </span> Developer Advocate.</h1>
<p class="text-lg font-normal text-gray-500 lg:text-xl dark:text-gray-400">Hello, I am Haimantika. Currently working as a Developer Advocate at Hashnode. I help B2D+ companies bridge the gap between developers and the product with code, content and community. As a community builder and a product of the community myself, I strongly believe that technology is for everyone. I am passionate about learning, sharing, and building with the community.</p>
</section>
Building the projects section using Flowbite's card component:
<main class="max-w-4xl mx-auto flex flex-col">
<section id="home" class="flex flex-col sm:flex-row p-20 items-center gap-8 mb-12">
<h1 class="mb-4 text-3xl font-extrabold text-gray-900 dark:text-white md:text-5xl lg:text-6xl"><span class="text-transparent bg-clip-text bg-gradient-to-r to-emerald-600 from-sky-400">Generalist & </span> Developer Advocate.</h1>
<p class="text-lg font-normal text-gray-500 lg:text-xl dark:text-gray-400">Hello, I am Haimantika. Currently working as a Developer Advocate at Hashnode. I help B2D+ companies bridge the gap between developers and the product with code, content and community. As a community builder and a product of the community myself, I strongly believe that technology is for everyone. I am passionate about learning, sharing, and building with the community.</p>
</section>
<main class="max-w-4xl mx-auto flex flex-col">
<section id="projects" class="flex flex-col sm:flex-row p-20 items-center gap-8 mb-12">
<div class="max-w-sm p-6 bg-white border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700">
<a href="#">
<h5 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">Melody Matcher</h5>
</a>
<p class="mb-3 font-normal text-gray-700 dark:text-gray-400">Happy? Sad? Angry? Want a music blend made just for you? Try out the soul music generator. Built using: Next.JS, and QuickBlox API.</p>
<a href="https://music-suggester.vercel.app/" class="inline-flex items-center px-3 py-2 text-sm font-medium text-center text-white bg-blue-700 rounded-lg hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
Try it here
<svg class="rtl:rotate-180 w-3.5 h-3.5 ms-2" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 10">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 5h12m0 0L9 1m4 4L9 9"/>
</svg>
</a>
</div>
<div class="max-w-sm p-6 bg-white border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700">
<a href="#">
<h5 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">Diss Track</h5>
</a>
<p class="mb-3 font-normal text-gray-700 dark:text-gray-400">Got hate comments? Time to turn it into a rap! Try out Diss Track. Built using: TailwindCSS (DaisyUI components), and QuickBlox API.</p>
<a href="https://disstrack.vercel.app/" class="inline-flex items-center px-3 py-2 text-sm font-medium text-center text-white bg-blue-700 rounded-lg hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
Try it here
<svg class="rtl:rotate-180 w-3.5 h-3.5 ms-2" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 10">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 5h12m0 0L9 1m4 4L9 9"/>
</svg>
</a>
</div>
</main>
Building the footer:
<footer class="bg-white rounded-lg shadow dark:bg-gray-900 m-4">
<div class="w-full max-w-screen-xl mx-auto p-4 md:py-8">
<div class="sm:flex sm:items-center sm:justify-between">
<ul class="flex flex-wrap items-center mb-6 text-sm font-medium text-gray-500 sm:mb-0 dark:text-gray-400">
<li>
<a href="https://www.linkedin.com/in/haimantika-mitra/" class="hover:underline me-4 md:me-6">LinkedIn</a>
</li>
<li>
<a href="#" class="hover:underline me-4 md:me-6">GitHub</a>
</li>
<li>
<a href="#" class="hover:underline me-4 md:me-6">Twitter</a>
</li>
<li>
<a href="#" class="hover:underline">Resume</a>
</li>
</ul>
</div>
</footer>
Part 2: Building the blog section
To build the blog section, we will make use of Hashnode in Headless mode and it's open-source starter kit.
The first step is to fork it and create your own copy.
Clone it locally using any IDE of your choice.
Currently, Hashnode has three themes: personal, hashnode and enterprise. You can use any, but in this demo, we will use the enterprise theme.
It's a monorepo, so cd into
packages/blog-starter-kit/themes/enterprise
, and then typepnpm install
to install all the necessary packages.The repository, will include a
.env.example
file. Copy the contents and create a.env
file that includes your blog publication details. You will only need to updateNEXT_PUBLIC_HASHNODE_PUBLICATION_HOST
, rest everything remains the same.
This is where you will find your Hashnode publication name, for me, it is haimantika.dev
👇
Once all of it is done, use the command
npm run dev
to run it locally.The starter kit uses Next.js and Tailwind, allowing you to customize the styling. In this demo, we modify the footer to match our homepage footer. The code for that looks like:
import Link from 'next/link'; import { Container } from './container'; import { useAppContext } from './contexts/appContext'; import { SocialLinks } from './social-links'; export const Footer = () => { return ( <footer className="bg-white rounded-lg shadow dark:bg-gray-900 m-4"> <div className="w-full max-w-screen-xl mx-auto p-4 md:py-8"> <div className="sm:flex sm:items-center sm:justify-between"> <ul className="flex flex-wrap items-center mb-6 text-sm font-medium text-gray-500 sm:mb-0 dark:text-gray-400"> <li> <a href="https://www.linkedin.com/in/haimantika-mitra/" className="hover:underline me-4 md:me-6">LinkedIn</a> </li> <li> <a href="#" className="hover:underline me-4 md:me-6">GitHub</a> </li> <li> <a href="#" className="hover:underline me-4 md:me-6">Twitter</a> </li> <li> <a href="#" className="hover:underline">Resume</a> </li> </ul> </div> </div> </footer> ); }
If you want to make other changes, check out this video:
Part 3: Deploying it using Vercel
We will need to deploy the homepage and the blog separately. To deploy the homepage, you can follow Vercel's documentation.
Let us learn how to deploy the blog section:
Create a new project on Vercel and connect this starter-kit repo.
Since it's a monorepo, and you choose the enterprise theme, you need to choose
packages/blog-starter-kit/themes/enterprise
as the root directory while importing on Vercel.Choose
Next.js
as framework preset.Set the following environment variables
NEXT_PUBLIC_HASHNODE_GQL_ENDPOINT=https://gql.hashnode.com NEXT_PUBLIC_HASHNODE_PUBLICATION_HOST=engineering.hashnode.com -> Change this to your Hashnode blog URL i.e. handle.hashnode.dev NEXT_PUBLIC_BASE_URL=/blog -> This could be /blog if you are installing on subpath; otherwise remove this var NEXT_PUBLIC_MODE=production
Then click on deploy. If you see a 404 error, do not worry, as you set the subpath as
/blog
you need to add it to Vercel's auto generated domain to see your blog.
Hosting your blog to your domains subpath
The next step is to map your blog to yourdomain.com/blog
. Currently, the homepage and blog is hosted in two different domains, but we want it to be on the same domain with the /blog
subpath. Something like 👉 https://haimantika.dev/blog
Since, we have used HTML and TailwindCSS to build the portfolio, we will need to work with Cloudflare workers.
Note: If you decide to build your portfolio with Next.js at any point in the future, you do not need to go through the Cloudflare route and you can simply follow the steps mentioned here.
Deploy the Worker, and updating the script and map it to yourdomain.com/blog
:
Login/Signup on Cloudflare.
Create and deploy worker as shown in the image below.
Once deployed, click on
Edit code
to update the worker script.Add the following code and save it.
const subpath = '/blog'; // Replace with your subpath const blogBaseUrl = 'https://yourdomain.com'; // Replace with your blog URL addEventListener('fetch', event => { event.respondWith(handleRequest(event.request)) }) /** * Respond to the request * @param {Request} request */ async function handleRequest(request) { const url = new URL(request.url) if (url.pathname.startsWith(subpath)) { // Proxy blog requests return proxyBlog(request) } else { // Passthrough everything else return fetch(request) } } /** * Proxy blog requests * @param {Request} request */ async function proxyBlog(request) { const path = new URL(request.url).pathname; return fetch(`${blogBaseUrl}${path}`, request) }
Once you've deployed the worker, the next step is to add the worker to your website domain on cloudfare.
For the domain you'll be using on your Cloudflare account, you need to add a worker route. The route should be
*
, and the worker should be the script you created in the steps above.Note: Make sure that your domain -
yourdomain.com
is on Cloudflare. If not, you should transfer it to make it work!For the final step, go to the Hashnode blog dashboard, and under the
Domain
section, make sure to turn on the Headless mode and mention the blog base URL.And now you are done! When you browse to
yourdomain.com/blog
you will see your blog live on the subpath!
Resources
Thank you for reading to the end. If you have followed the tutorial or the webinar and created a portfolio, feel free to tag us on X (Twitter) @Hashnode, and we will retweet it!
Here are some resources to help you learn more: