How I created a minimal linktree like page for me in just 2 hours.
Tags: javascript, webdev, react, tutorial
Published: 2022-05-21 05:20:07

## 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:
```js
// 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'
        },
        ...
    ]
}
```
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:
- NextJS 
- Tailwind
- [React-Tooltip](https://wwayne.github.io/react-tooltip)
- [React-Hot-Toast](https://github.com/timolins/react-hot-toast)
- [Simple-Icons](https://simpleicons.org/)

## Creating the App
First, I quickly started a next project using the beloved command `create-next-app`, [initialized](https://tailwindcss.com/docs/guides/nextjs) 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.
```js
// 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"/>
    )
}
```

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.
```js
// GetIcon.js

import SimpleIcons from 'simple-icons';

export default function GetIcon(slug) {
    const icon = SimpleIcons.Get(slug).svg
    return icon;
}
```
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.
```js
// 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>
    )
}
```
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.
```js
// 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>
    )
}
```
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:
```js
// 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>
  )
}
```

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

![ss](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m8gpakyh63xouli04gfl.png)

## 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](https://github.com/asheeeshh/ayyy).

Thanks for reading <3