How I created a minimal linktree like page for me in just 2 hours.
ashish

ashish @asheeshh

About: student and solo dev by passion who likes writing occasionally

Location:
Under a Blanket
Joined:
Oct 6, 2021

How I created a minimal linktree like page for me in just 2 hours.

Publish Date: May 21 '22
88 23

The Plan

I have seen a lot of people using linktree and similar sites where you can create your social links page, but I needed something very minimal and clean, and thus I decided to make my own social links page!

NOTE: I tried to explain the process as best as I can but I'm still a newbie to blogging, so please don't mind if it seems weird at some places and let me know where I can improve, I'd love to hear from you.

Design

The design was pretty clear to me, a small avatar, name, bio and all the social links as icons with a cool hover effect. I wanted to make it easily customizable if I needed to so I knew I had to implement a config file with all the colors, icon list, name, bio and avatar link. It looks like this:

// config.js 
export const config = {
    avatar: 'https://avatars.githubusercontent.com/u/68690233',
    bgColor: '#18181b',
    textColor: '#d4d4d8',
    iconColor: '#d4d4d8',
    name: 'ashish',
    description: 'solo developer by day, overthinker by night.',
    links: [
        {
            slug: 'github',
            type: 'url',
            link: 'https://github.com/asheeeshh/'
        },
        {
            slug: 'discord',
            type: 'hover',
            text: 'asheeshh#7727'
        },
        ...
    ]
}
Enter fullscreen mode Exit fullscreen mode

Note how I'm using type: 'hover' for discord to distinguish it from other icons, keep reading to know the reason.

Tech Stack

As it was just a single page app I decided to use NextJS as I'm very comfortable with it at the moment. Here are all the frameworks and libraries I used:

Creating the App

First, I quickly started a next project using the beloved command create-next-app, initialized tailwind CSS in the project and installed all the other libraries I needed.

The next step was to create all the components I needed, that are Avatar.jsx, Icon.jsx and IconBar.jsx.

Components

  • Avatar.jsx - the avatar component in the app.
  • Icon.jsx - individual icon component.
  • IconBar.jsx - the horizontal icon bar component in the app.

Now, let's discuss about the content of these files.

Here is the code for my Avatar.jsx file. It's a Next Image Component with tailwind class.

// Avatar.jsx

import Image from 'next/image'

export default function Avatar() {
    return (
        <Image src="https://avatars.githubusercontent.com/u/68690233" alt="Avatar" width={100} height={100} className="rounded-full"/>
    )
}
Enter fullscreen mode Exit fullscreen mode

For the Icons, I'm using Simple-Icons, as they have a lot of brand icons which was exactly what I needed. First, I created a file GetIcon.js to get the SVG Icon using the slug. It looks something like this.

// GetIcon.js

import SimpleIcons from 'simple-icons';

export default function GetIcon(slug) {
    const icon = SimpleIcons.Get(slug).svg
    return icon;
}
Enter fullscreen mode Exit fullscreen mode

As you can see, it returns the <svg></svg> tag of the icon as a string. The next step was converting the string to a jsx component which is what my Icon.jsx component does.

// Icon.jsx

import GetIcon from "../libs/GetIcon";
import { config } from "../config";

export default function Icon(props) {
    return (
        <div dangerouslySetInnerHTML={{__html: `${GetIcon(props.icon)}`}} className="w-[30px] h-[30px] hover:scale-[1.15]  duration-300 ease-in-out" style={{fill: `${config.iconColor}`}}></div>
    )
}
Enter fullscreen mode Exit fullscreen mode

You can see that I'm using config to set the icon color. It takes the icon slug as props and passes it to GetIcon() which returns the svg as string which is converted to a jsx component by using dangereouslySetInnerHTML

The last component is IconBar.jsx which stacks all the Icons horizontally and returns them as a jsx component.

// IconBar.jsx

import Icon from "./Icon";
import { config } from "../config";
import ReactTooltip from 'react-tooltip';
import { useEffect, useState } from "react";
import toast, { Toaster } from 'react-hot-toast';

export default function IconBar() {
    const [isMounted, setIsMounted] = useState(false)
    useEffect(() => {
        setIsMounted(true)
    }, [])
    const handleClick = (e) => {
        navigator.clipboard.writeText(e.target.closest('[data-tip]').dataset.tip)
        toast.success("Copied to clipboard!", {
            duration: 2000,
        })
    }
    const icons = config.links.map(
        (icon) => {
            if (icon.type == "url") {
                return (
                    <div className="text-center items-center cursor-pointer" key={icon.slug}>
                        <a href={icon.link} target="_blank" rel="noopener noreferrer" >
                            <Icon icon={icon.slug} />
                        </a>
                    </div>
                );
            } else if (icon.type == "hover") {
                return (
                    <div className="text-center items-center cursor-pointer" key={icon.slug}> 
                        <a data-tip={icon.text} key={icon.slug} onClick={handleClick}>
                            <Icon icon={icon.slug} />
                        </a>
                        {isMounted && <ReactTooltip place="top" type="dark" effect="float"/>}
                    </div>
                )
            } else {
                return;
            }
        }
    )
    return (
        <div className="flex flex-wrap w-full h-full gap-5 justify-center items-top">
            <Toaster 
                toastOptions={{
                    style: {
                        background: `${config.textColor}`
                    }
                }}
            />
            {icons}
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

I'm mapping the array present in my config.js file to icons to convert them to <div></div> components which is finally used in the returned div which has. Also, since discord doesn't have an URL but has a tag I used React-Tooltip to make a tooltip for the discord icon. That's the reason why I had added type: 'hover' in discord icon as stated above.

To show the notification that the discord tag has been copied, I used the React-Hot-Toast library.

Assembling Components

The final step was to assemble all the components in my index.js file to complete the app. Here's what it looks like:

// index.js

import Avatar from "../components/Avatar"
import IconBar from "../components/IconBar"
import { config } from "../config"
import Head from "next/head"

export default function Home() {
  return (
    <div className="flex flex-col justify-center items-center w-screen h-screen p-6" style={{backgroundColor: `${config.bgColor}`}}>
      <Head>
        <title>{config.name}</title>
        <meta name="description" content={config.description} />
        <link rel="icon" href={(process.env.NEXT_PUBLIC_CLOUDIMG_TOKEN) ? `https://${process.env.NEXT_PUBLIC_CLOUDIMG_TOKEN}.cloudimg.io/${config.avatar}?radius=500` : `${config.avatar}`} />
      </Head>
      <div className="flex flex-col justify-center align-center w-full lg:w-1/2 md:w-1/3 h-[80%] lg:h-1/2 md:h-1/2 items-center">
        <div className="w-full h-full flex flex-col justify-center items-center">
          <Avatar />
          <h1 className="text-center text-xl font-[600] mt-3" style={{color: `${config.textColor}`}}>{config.name}</h1>
          <h1 className="text-[${config.textColor}] text-center text-md font-normal mt-5" style={{color: `${config.textColor}`}}>{config.description}</h1>
          <div className="w-full h-1/4 mt-5 lg:mt-3 md:mt-3">
            <IconBar />
          </div>
        </div>
      </div>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

After assembling, and a bit of styling using tailwind this is what the App looks like:

ss

Deploying the app

I used vercel to deploy the app, as it works best with NextJS, and added a custom subdomain to it. The site is live at https://ayyy.vercel.app/ and https://ayyy.asheeshh.ninja/ currently.

Conclusion

This was the whole process of making the app, and it roughly took around 2 hours to make, I'm already using it as my social link page currently.

You're free to use it to create your own page if you want to, the source code is available under MIT License here.

Thanks for reading <3

Comments 23 total

  • Andrew Baisden
    Andrew BaisdenMay 21, 2022

    Cool project and its responsive well done.

    • ashish
      ashish May 21, 2022

      thank you so much <3

  • Khokon M.
    Khokon M.May 21, 2022

    Isn't it a bit overkill for such a simple page?
    Just casually asking, so that I can discover whats your perspective to use this much tech on just a simple page.

    • codeyStein
      codeySteinMay 21, 2022

      I'm not the author and I'm still learning React, but in my personal oppinion, it might be a little over complicated, but I think makes it easier to update things when needed.

    • SIMBIOSIS
      SIMBIOSISMay 21, 2022

      I think that it's a pretty piece of an idea. Maybe you perceive it overkillig if you just look at it as it is.... But, if you are able to forsee all the possibilities it brings starting from here, it's a real good practice to do things well since the beginning.

    • ashish
      ashish May 21, 2022

      I get your point, actually while making it even I thought that maybe I'm overengineering the site but the thing is, actually I didn't if you look at it from my perspective. I've been using NextJS for quite some time now, and at the moment, it's far more faster and productive for me to use Next than any other framework/method, styling using tailwind was also because I didn't want to give much time to styling or I couldn't have been able to complete the project in this short span of time. About other libraries I used, both react-hot-toast and react-tooltip are quite minimal libraries and thus they hardly affect the bundle size.

    • Khokon M.
      Khokon M.May 22, 2022

      Thanks guys. I got what I needed 😇

  • Tilak Jain
    Tilak JainMay 21, 2022

    Awesome Project!

  • SIMBIOSIS
    SIMBIOSISMay 21, 2022

    Congratulations. You made a great job here.

    • ashish
      ashish May 21, 2022

      thank you so much ❤

  • Prudence97
    Prudence97May 21, 2022

    Amazing ☺️☺️☺️

  • Roman
    RomanMay 21, 2022

    Nice

  • Fernando Groders
    Fernando GrodersMay 22, 2022

    Pretty clean, I like it 🔥

    • ashish
      ashish May 22, 2022

      thank you so much ❤

  • Anurag Singh
    Anurag SinghMay 22, 2022

    Yeah, I prefer this approach for a minimalistic portfolio site - check mine

    frankly, yours looking way better, clean af!

    • ashish
      ashish May 22, 2022

      thanks! actually it was not meant to be a portfolio site, it's just a site to share all my social links and contacts :)

      I checked your site and it looks so much similar to mine, just a suggestion, you can reduce the heading font size and change the font style, also there's unnecessary scroll on the body so maybe hide it with overflow-y: hidden; 🤔

      • Anurag Singh
        Anurag SinghMay 22, 2022

        Yeah, I wrote it long back but I feel now is the perfect time to revisit and make some necessary improvements to it, thanks for the tip btw!

  • Darko Riđić
    Darko RiđićMay 28, 2022

    Nice!

  • Luca
    LucaJun 1, 2022

    Nice looking, to me it's a bit overkill to use nextjs for such a page (big fan of vanilla js), but I understand you wanted to be able to expand the project without having to completely rewrite.
    Tell the truth, 2 hours completing but 3/4 of it for running npm install 😁?
    (Joking)

    • ashish
      ashish Jun 1, 2022

      😂🤫 sshh, don't let them know

Add comment