Next.js + Tailwind CSS + TypeScript Starter Template
Tags: nextjs, tailwindcss, react, typescript
Published: 2021-08-13 10:51:51

Setting up a new project and connecting up everything takes a lot of time. So I decided to create a starter template with my favorite tech stack for easy use.

[![banner](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/shl3sf1p01jvwmamdoah.png)](https://github.com/theodorusclarence/ts-nextjs-tailwind-starter)
 
{% github theodorusclarence/ts-nextjs-tailwind-starter no-readme %}

## What's Inside

### 1. Installed Packages
- [clsx](https://bundlephobia.com/package/clsx@latest), utility for constructing `className` strings conditionally. 
- [react-icons](https://bundlephobia.com/package/react-icons@latest), svg react icons of popular icon packs.

### 2. UnstyledLink Component
Used as a component for Next.js Link. Will render out Next/Link if the href started with `/` or `#`, else will render an `a` tag with `target='_blank'`. Also add a cursor style for outside links.

I found that by using this component, it simplifies the boilerplate for Next.js Link which uses `Link` and `a` tag.

```tsx
// src/components/UnstyledLink.tsx
import clsx from 'clsx';
import Link, { LinkProps } from 'next/link';

export type UnstyledLinkProps = {
  href: string;
  children: React.ReactNode;
  openNewTab?: boolean;
  className?: string;
} & React.ComponentPropsWithoutRef<'a'> &
  LinkProps;

export default function UnstyledLink({
  children,
  href,
  openNewTab,
  className,
  ...rest
}: UnstyledLinkProps) {
  const isNewTab =
    openNewTab !== undefined
      ? openNewTab
      : href && !href.startsWith('/') && !href.startsWith('#');

  if (!isNewTab) {
    return (
      <Link href={href}>
        <a {...rest} className={className}>
          {children}
        </a>
      </Link>
    );
  }

  return (
    <a
      target='_blank'
      rel='noopener noreferrer'
      href={href}
      {...rest}
      className={clsx(className, 'cursor-[ne-resize]')}
    >
      {children}
    </a>
  );
}
```

### 3. CustomLink Component
Using the UnstyledLink component, we can extend it to a new Component with all the default styles. Here are some styles that I baked into the starter for easy use.
![customlink](https://user-images.githubusercontent.com/55318172/129183546-4e8c2059-0493-4459-a1e9-755fbd32fe39.gif)

### 4. Absolute Import
I'm a fan of [Vue style absolute import](https://stackoverflow.com/questions/42749973/what-does-the-mean-inside-an-import-path), so I also added the configuration on tsconfig.json, so we can just do importing like so.
```tsx
import Nav from '../../../components/Nav';

simplified to

import Nav from '@/components/Nav';
```

### 5. Seo Component
Configure the default in `src/components/Seo.tsx`. If you want to use the default, just add `<Seo />` on top of your page. 

You can also customize it per page by overriding the title, description as props

```tsx
<Seo
  title='Next.js Tailwind Starter'
  description='your description'
/>
```

or if you want to still keep the title like `%s | Next.js Tailwind Starter`, you can use `templateTitle` props.

Here is the Seo component that I'm using, you can just simply change the defaults and you're good to go

```tsx
// src/components/Seo.tsx
import Head from 'next/head';
import { useRouter } from 'next/router';

const defaultMeta = {
  title: 'Next.js Tailwind Starter',
  site_name: 'Next.js Tailwind Starter',
  description: 'A template for Next.js and Tailwindcss by Theodorus Clarence',
  url: 'https://theodorusclarence.com',
  image: 'https://theodorusclarence.com/favicon/large-og.jpg',
  type: 'website',
  robots: 'follow, index',
};

type SeoProps = {
  date?: string;
  templateTitle?: string;
} & Partial<typeof defaultMeta>;

export default function Seo(props: SeoProps) {
  const router = useRouter();
  const meta = {
    ...defaultMeta,
    ...props,
  };
  meta['title'] = props.templateTitle
    ? `${props.templateTitle} | ${meta.site_name}`
    : meta.title;

  return (
    <Head>
      <title>{meta.title}</title>
      <meta name='robots' content={meta.robots} />
      <meta content={meta.description} name='description' />
      <meta property='og:url' content={`${meta.url}${router.asPath}`} />
      <link rel='canonical' href={`${meta.url}${router.asPath}`} />
      {/* Open Graph */}
      <meta property='og:type' content={meta.type} />
      <meta property='og:site_name' content={meta.site_name} />
      <meta property='og:description' content={meta.description} />
      <meta property='og:title' content={meta.title} />
      <meta name='image' property='og:image' content={meta.image} />
      {/* Twitter */}
      <meta name='twitter:card' content='summary_large_image' />
      <meta name='twitter:site' content='@th_clarence' />
      <meta name='twitter:title' content={meta.title} />
      <meta name='twitter:description' content={meta.description} />
      <meta name='twitter:image' content={meta.image} />
      {meta.date && (
        <>
          <meta property='article:published_time' content={meta.date} />
          <meta
            name='publish_date'
            property='og:publish_date'
            content={meta.date}
          />
          <meta
            name='author'
            property='article:author'
            content='Theodorus Clarence'
          />
        </>
      )}
    </Head>
  );
}
```

### 6. Custom 404 Page
The Next.js default 404 is pretty simple, so I added some base styles, and some flickering animation.
![404](https://user-images.githubusercontent.com/55318172/129184274-d90631f2-6688-4ed2-bef2-a4d018a4863c.gif)


### 7. Workspace Snippets
Snippets such as React Import, useState, useEffect, React Component, and Next.js Pages with Seo import included. [View more](https://github.com/theodorusclarence/ts-nextjs-tailwind-starter/blob/main/.vscode/typescriptreact.code-snippets)

![workspace-snippets](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/twdpxm9s90gz4gbvw5b0.gif)
 

### 8. Husky, Prettier, Lint, and Commitlint Configured
I installed 3 Husky hooks including:
1. pre-commit, running eslint and format the code using prettier
2. commit-msg, running commitlint to ensure the use of [Conventional Commit](https://theodorusclarence.com/library/conventional-commit-readme) for commit messages
3. post-merge, running `yarn` every `git pull` or after merge to ensure all new packages are installed and ready to go

### 9. Default Favicon Declaration
Use [Favicon Generator](https://www.favicon-generator.org/) and then overwrite the files in `/public/favicon`

### 10. Default Tailwind CSS Base Styles
There are default styles for responsive heading sizes, and `.layout` to support a max-width for larger screen size. Find more about it on [my blog post](https://theodorusclarence.com/blog/tailwindcss-best-practice#1-using-layout-class-or-container)

### 11. Preloaded & Self Hosted Inter Fonts
Inter is a variable fonts, that is self hosted and preloaded in this starter.

{% github theodorusclarence/ts-nextjs-tailwind-starter no-readme %}

Check it out on github! Please do open a PR if you want to contribute! Let me know if there are something that can be improved.
Star ⭐ is much appreciated!