🌙

Let's build Dev.to clone with Next.js & ChakraUI

nextjswebdevreactbeginners
Loading...
 

15

 

Published on

June 26, 2021

Let's clone dev.to with the actual dev.to api to get the posts and listings.

Overview

This application is built with the following technologies:

Live demo: https://dev-to-clone-ab.vercel.app

Github repo: https://github.com/MA-Ahmad/dev.to-clone

Breaking down the layout of Dev.to

  • Top navbar
    • Profile menu dropdown
  • Left sidebar
  • Posts section
  • Right sidebar

I'll discuss some components code here otherwise this article will become very long.

1. Setup the Project

Create a Next.js app

yarn create next-app --typescript

Install chakra-ui

yarn add @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^4

2. Start coding

Top navbar

const Navbar = () => {
  return (
    <Box
      py="2"
      boxShadow="sm"
      border="0 solid #e5e7eb"
      position="fixed"
      top="0"
      bg="#fff"
      width="100%"
      zIndex="1"
    >
      <Container>
        <HStack spacing={4}>
          <Image src="/assets/images/logo.svg" />
          <Input
            maxW="26rem"
            placeholder="Search..."
            borderColor="#b5bdc4"
            borderRadius="5px"
            d={{ base: "none", md: "block" }}
          />
          <Spacer />
          <HStack spacing={3}>
            <Button
              color="#fff"
              borderRadius="4px"
              bg="#3b49df"
              _hover={{ bg: "#323ebe" }}
            >
              Create a post
            </Button>
            <IconButton>
              <Image src="/assets/images/notification.svg" />
            </IconButton>
            <IconButton>
              <Image src="/assets/images/bell.svg" />
            </IconButton>
            <Avatar
              size={"sm"}
              src={"https://avatars2.githubusercontent.com/u/37842853?v=4"}
            />
          </HStack>
        </HStack>
      </Container>
    </Box>
  );
};

Navbar

Update

Created profile menu dropdown menu-dropdown

Left sidebar

Links section

const Links = () => {
  return (
    <Box as="nav">
      <LinkButton>
        <Image src="/assets/images/sidebar/home.svg" mr="3" />
        Home
      </LinkButton>
      <LinkButton>
        <Image src="/assets/images/sidebar/reading.svg" mr="3" />
        Reading List
      </LinkButton>
      <LinkButton>
        <Image src="/assets/images/sidebar/tag.svg" mr="3" />
        Tags
      </LinkButton>
      <LinkButton>
        <Text fontWeight="normal" color="#4d5760" ml="2.3rem">
          More...
        </Text>
      </LinkButton>
    </Box>
  );
};

Tags section

const Tags = () => {
  return (
    <Box mt="6">
      <Flex pl="2" py="4">
        <Heading as="h3" fontSize="1rem">
          My Tags
        </Heading>
        <Spacer />
        <Image src="/assets/settings.svg" />
      </Flex>
      <Box maxH="50vh" overflowY="auto">
        <TagList>
          {[
            "Nextjs",
            "react",
            "javascript",
            "ruby",
            "ruby on rails",
            "css",
            "beginners",
            "html",
            "typescript"
          ]}
        </TagList>
      </Box>
    </Box>
  );
};

left-sidebar

Posts section

End point for showing feed https://dev.to/stories/feed Post card component

<Box
      mt="3"
      as="article"
      bg="white"
      borderRadius="md"
      overflow="hidden"
      border="1px solid #08090a1a"
    >
      {headerImage ? <Image src={headerImage} /> : ""}
      <Grid templateColumns="max-content 1fr" gap={2} p={4}>
        <Image src={userProfile} w="8" borderRadius="full" />

        <Box>
          <VStack align="flex-start" spacing={0}>
            <Text color="#4d5760" fontSize="14px" fontWeight="500">
              {username}
            </Text>
            <Text color="#4d5760" fontSize="12px">
              {publishedDate}
            </Text>
          </VStack>
          <Heading fontSize={headerImage ? "30px" : "24px"} mt="3">
            <Link
              href={postLink}
              _hover={{ color: "#323ebe", textDecoration: "none" }}
              isExternal
            >
              {title}
            </Link>
          </Heading>
          <HStack mt="3" fontSize="14px" color="#64707d">
            {tagList.map((tag, idx) => (
              <Text as={Link} key={idx}>
                #{tag}
              </Text>
            ))}
          </HStack>
          <HStack mt={3}>
            <Button
              leftIcon={<Image src="/assets/images/like.svg" />}
              ml={-2}
              bg="transparent"
              padding="6px 8px"
              height="auto"
              fontWeight="normal"
              fontSize="14px"
              lineHeight="1.2"
              borderRadius="4px"
              _hover={{ bg: "#f6f6f6" }}
            >
              {reactionCount} reactions
            </Button>
            <Button
              leftIcon={<Image src="/assets/images/comment.svg" />}
              bg="transparent"
              padding="6px 8px"
              height="auto"
              fontWeight="normal"
              fontSize="14px"
              lineHeight="1.2"
              borderRadius="4px"
              _hover={{ bg: "#f6f6f6" }}
            >
              {commentCount} comments
            </Button>
            <Spacer />
            <Text fontSize="12px">{readingTime} min read</Text>
            <Button
              bg="#d2d6db"
              padding="8px 12px"
              height="auto"
              fontWeight="normal"
              fontSize="14px"
              _hover={{ bg: "#b5bdc4" }}
            >
              Save
            </Button>
          </HStack>
        </Box>
      </Grid>
    </Box>

posts skeleton posts

Right sidebar

End point for showing list https://dev.to/api/listings

const List = () => {
  const { data, error } = useSWR("https://dev.to/api/listings", fetcher);

  if (error) return <Box>failed to load</Box>;
  if (!data)
    return (
      <Box as="section" bg="white" borderRadius="md" border="1px solid #E2E4E6" width="100%">
        <ListHeading />
        {[1, 2, 3, 4, 5].map(id => {
          return (
            <Box borderBottom="1px solid #E2E4E6" width="100%" p="3">
              <Skeleton height="15vh" borderRadius="5px" width="100%" />
            </Box>
          );
        })}
      </Box>
    );

  return (
    <Box as="section" bg="white" borderRadius="md" border="1px solid #E2E4E6">
      <ListHeading />
      {data.slice(0, 7).map(list => (
        <ListBox title={list.title} category={list.category} slug={list.slug} />
      ))}
    </Box>
  );
}

Listing

Responsiveness

responsiveness responsiveness