How we developed our company website using NextJS
Joseph Mukorivo
Posted on October 17, 2022
I sat down with my close friends that I had been working with for a while at the start of 2022 to discuss about starting a software company that merged aesthetics and functionality. We made the decision to combine our various skill sets to form that business, and complexus was established.
We completed a few projects for other clients before deciding to concentrate on our website. We began by conducting research on our target market, and then the team began to work on the initial iteration of the site's design.
Overview
In this article, I'll primarily discuss the tools we utilized and how we used them to create our dynamic website. These are the tools we typically employ for our clients' projects.
Design
The design team used figma to create the site's design, and a screenshot of the initial version is seen below. The design was finished after four iterations, the first of which was a low-fidelity wireframe. Almost everyone contributed to the design in some way.
Content
We had someone in charge of copywriting for the site's material, and they collaborated with the design team to ensure that what they were writing was consistent with the design.
Development
We went on to build the website after creating the design and the content. Let me list some of the things that the website is powered by before I get into the technicalities.
- React for the UI
- NextJS for SSG/SSR/ISSG
- Tailwind css for styling
- CSS modules styling without class name collision
- GSAP JavaScript animations
- Mailchimp for the mailing list
- Vercel CI/CD and hosting
- Framer motion JavaScript animations
- TypeScript for adding type safety to our codebase
I will probably spend a little more time at this stage because development was the most fascinating phase. Reactjs is the foundation of the website and all the components were built upon it. Below is a sample reusable Button
component and its styles.
Button.tsx
import {
FC,
forwardRef,
ButtonHTMLAttributes,
JSXElementConstructor,
ReactElement,
} from 'react';
import Link from 'next/link';
import { motion } from 'framer-motion';
import cn from 'classnames';
import s from './Button.module.scss';
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
href?: string;
className?: string;
disabled?: boolean;
rightIcon?: ReactElement;
leftIcon?: ReactElement;
active?: boolean;
target?: '_blank' | '_self' | '_parent' | '_top';
size?: 'sm' | 'md' | 'lg';
variant?:
| 'primary'
| 'secondary'
| 'white'
| 'black'
| 'naked'
| 'outlineBlack'
| 'outlineWhite'
| 'square';
as?: 'button' | 'a' | JSXElementConstructor<any>;
}
export const Button: FC<ButtonProps> = forwardRef((props, buttonRef) => {
const {
as: Tag = 'button',
variant = 'primary',
size = 'md',
target = '_self',
href,
active,
rightIcon,
leftIcon,
className,
disabled,
children,
...rest
} = props;
const classes = cn(
s.root,
{
[s.primary]: variant === 'primary',
[s.outlineBlack]: variant === 'outlineBlack',
[s.outlineWhite]: variant === 'outlineWhite',
[s.secondary]: variant === 'secondary',
[s.white]: variant === 'white',
[s.naked]: variant === 'naked',
[s.black]: variant === 'black',
[s.square]: variant === 'square',
[s.md]: size === 'md',
[s.sm]: size === 'sm',
[s.lg]: size === 'lg',
'flex items-center gap-3': (rightIcon || leftIcon) && size === 'md',
[s.dFlex]: (rightIcon || leftIcon) && size === 'lg',
[s.active]: active,
},
className
);
return (
<motion.span
className='inline-block'
initial={{ y: 10, opacity: 0 }}
transition={{
duration: 1,
type: 'spring',
damping: 5,
}}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.5 }}
>
{href ? (
<Tag {...rest}>
<Link href={href}>
<a className={classes} target={target}>
{leftIcon}
<span>{children}</span>
{rightIcon}
</a>
</Link>
</Tag>
) : (
<Tag disabled={disabled} className={classes} {...rest}>
{leftIcon}
<span>{children}</span>
{rightIcon}
</Tag>
)}
</motion.span>
);
});
Button.displayName = 'Button';
Button.module.scss
.root {
@apply mb-1 transition duration-200 ease-linear;
}
.dFlex {
@apply flex items-center gap-5;
}
.primary {
@apply relative overflow-hidden border border-primary bg-primary text-white;
span,
svg {
@apply relative z-[2];
}
&::after {
content: '';
@apply absolute top-0 left-0 block h-full w-0 bg-white transition-[width] duration-300 ease-linear;
}
&:hover {
@apply text-primary;
}
&:hover::after {
@apply w-full;
}
}
.naked {
@apply bg-transparent;
}
.black {
@apply relative border border-black bg-black text-white;
&::after {
content: '';
@apply absolute top-0 left-0 block h-full w-0 bg-white mix-blend-difference transition-[width] duration-300 ease-linear;
}
&:hover::after {
@apply w-full;
}
}
.white {
@apply border border-white bg-white text-primary;
}
.secondary {
@apply border border-secondary bg-secondary text-white;
}
.outlineBlack {
@apply relative border border-black;
&::after {
content: '';
@apply absolute top-0 left-0 block h-full w-0 bg-white mix-blend-difference transition-[width] duration-300 ease-linear;
}
&:hover::after {
@apply w-full;
}
}
.square {
@apply relative flex h-12 w-12 items-center justify-center rounded-full border border-black p-0 hover:scale-110 3xl:h-20 3xl:w-20 3xl:border-2 #{!important};
}
.outlineWhite {
@apply relative border border-white text-white;
&::before {
content: '';
@apply absolute left-[20%] -bottom-[1px] h-[1px] w-7 bg-black transition-[left];
}
&::after {
content: '';
@apply absolute right-[20%] -top-[1px] h-[1px] w-7 bg-black transition-[right];
}
&:hover::before {
@apply left-[30%];
}
&:hover::after {
@apply right-[30%];
}
&:hover {
@apply opacity-100;
}
}
.sm {
@apply px-4 py-2;
}
.md {
@apply px-4 py-[10px] xl:px-6;
}
.lg {
@apply px-5 py-4 text-sm font-medium xl:py-[1.1rem] xl:px-8 xl:text-base 3xl:px-16 3xl:py-6 3xl:text-lg;
}
The components were code using TypeScript for type safety. TypeScript also helps with writing code that is self documenting. For styling we went with tailwindcss
but note that classes in our react components are clean because the tailwind utility classes are in a separate css file which is a CSS module
. CSS modules helps in avoiding namespace collision for CSS classes. Below will be how the Button
can be used.
<Button
variant='black'
size='lg'
href='/contact'
className='relative ml-2'
rightIcon={<Line />}
>
Get in touch
</Button>
All of our reusable components are coded this way.
These small components like the Button
, Text
, Link
and Box
are located in the components/ui/
folder and exported using using a single index.ts
file so that they can be imported like so
import { Text, Box, Container, Link, Button } from '@components/ui';
Common elements like the Navigation
and Footer
are in the components/shared/
folder and elements that are specific to a page are in the components/pages/[page-name]
folder.
SEO
We had to make sure that our website is search engine friendly so we created a Page
component that takes some SEO data as props and every page on the site uses it as a parent.
Page.tsx
import { FC } from 'react';
import Head from 'next/head';
interface Props {
title: string;
description: string;
image: string;
canonicalURL?: string;
}
export const Page: FC<Props> = ({
children,
title,
description,
image,
canonicalURL,
}) => {
return (
<>
<Head>
<title>{title}</title>
<meta name='description' content={description} />
<meta name='author' content='Complexus Technologies' />
<meta name='image' content={image} />
<meta name='og:title' content={title} />
<meta name='og:description' content={description} />
<meta name='og:image' content={image} />
<meta name='og:url' content='https://complexus.tech' />
<meta name='og:site_name' content='Complexus Technologies' />
<meta name='og:type' content='website' />
<meta name='twitter:card' content='summary_large_image' />
<meta name='twitter:title' content={title} />
<meta name='twitter:alt' content={title} />
<meta name='twitter:description' content={description} />
<meta name='twitter:image' content={image} />
<meta name='theme-color' content='#f43f5e' />
<meta name='twitter:site' content='@complexus_tech' />
<meta name='twitter:creator' content='@complexus_tech' />
{canonicalURL && <link rel='canonical' href={canonicalURL} />}
</Head>
<main>{children}</main>
</>
);
};
Example Page usage
import type { NextPage } from 'next';
import { Page, HowMuch } from '@components/shared';
import { Hero, Intro, Team } from '@components/pages/about';
const Home: NextPage = () => {
return (
<Page
title='About | Complexus Technologies'
description='Complexus helps you achieve your business goals through effective planning, design, and development.'
image='https://complexus.tech/images/banner.jpg'
url='https://complexus.tech/about/'
canonicalURL='https://complexus.tech/about/'
keywords='complexus, complexus technologies, complex, zimbabwe sofware company, mobile development, it consultancy'
>
<Hero />
<Intro />
<Team />
<HowMuch />
</Page>
);
};
export default Home;
We also used other tools like Google Analytics, Google My Business and Google search console for SEO.
We also used some open graph tags to make sure that the site has some nice priviews when we share it on social media.
Mailing list
In order to share some information about events at Complexus, we are running a mailing list using Mailchimp. If you subscribe we'd be grateful😎.
Hosting
The website is hosted on Vercel and it rebuilds every time we push to the main branch on github.
CTA
If your organization would like to have a website similar to ours, please contact us here or email us at hello@complexus.tech. We also do custom software development check our services here.
Posted on October 17, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.