How to Build a Developer Blog using Next JS 13 and Contentlayer – Part three

how-to-build-a-developer-blog-using-next-js-13-and-contentlayer-–-part-three

Add more directories and pages

Make directories: about, blog, projects under the app directory. Create a page.tsx in each directory. Since the layout is not changing you donโ€™t need to have a layout.tsx file in each directory. Placeholder text is included for app/about/page.tsx.

About Page
app/about/page.tsx

import React from "react";

const About = () => {
  return (
    

Lorem ipsum dolor sit amet consectetur adipisicing elit. Ex quos laborum aut voluptates qui vitae incidunt iusto ipsam, nam molestiae reprehenderit quisquam cum molestias ut nesciunt? Culpa incidunt nobis libero?

Voluptate natus maiores, alias sapiente nisi possimus?

Ex amet eu labore nisi irure sit magna. Culpa minim dolor consequat dolore pariatur deserunt aliquip nisi eu ex dolor pariatur enim. Lorem pariatur cillum ullamco minim nulla ex voluptate. Occaecat esse mollit ipsum magna consectetur nulla occaecat non sit sint amet. Pariatur quis duis ut laboris ipsum velit fugiat do commodo consectetur adipisicing ut reprehenderit.

); }; export default About;

Blog Page
app/blog/page.tsx

import React from "react";
import { allPosts } from "contentlayer/generated";
import { compareDesc } from "date-fns";
import PostCard from "@/components/PostCard";

import "../../app/globals.css";

const Blog = () => {
  const posts = allPosts.sort((a, b) =>
    compareDesc(new Date(a.date), new Date(b.date))
  );
  return (
    

Developer Blog

{posts.map((post, idx) => (

))}
); }; export default Blog;

Projects Page
This page contains three components.
components/Fork.tsx which draws a fork.

https://github.com/donnabrown77/developer-blog/blob/main/components/Fork.tsx
components/Star.tsx which draws a star.
https://github.com/donnabrown77/developer-blog/blob/main/components/Star.tsx

components/Card.tsx which displays the github project data display in a card.
https://github.com/donnabrown77/developer-blog/blob/main/components/Card.tsx

Card.tsx uses types created in the file types.d.ts in the root directory.

export type PrimaryLanguage = {
  color: string;
  id: string;
  name: string;
};

export type Repository = {
  description: string;
  forkCount: number;
  id?: number;
  name: string;
  primaryLanguage: PrimaryLanguage;
  stargazerCount: number;
  url: string;
};

type DataProps = {
  viewer: {
    login: string;
    repositories: {
      first: number;
      privacy: string;
      orderBy: { field: string; direction: string };
      nodes: {
        [x: string]: any;
        id: string;
        name: string;
        description: string;
        url: string;
        primaryLanguage: PrimaryLanguage;
        forkCount: number;
        stargazerCount: number;
      };
    };
  };
};

export type ProjectsProps = {
  data: Repository[];
};

export type SvgProps = {
  width: string;
  height: string;
  href?: string;
};

You can provide a link to your github projects instead of accessing them this way but I wanted to display them on my website instead of making the users leave.

You will need to generate a personal access token from github. The github token is included in the .env.local file in this format:
GITHUB_TOKEN=”Your token”

Go to your Github and your profile. Choose Settings. Itโ€™s near the bottom of the menu.

Go to Developer Settings. Itโ€™s at the bottom of the menu. Go to Personal access tokens.

Choose generate new token ( classic ). Youโ€™ll see a menu with various permissions you can check. Everything is unchecked by default. At a minimum, you will want to check โ€œpublic_repoโ€, which is under โ€œrepoโ€, and youโ€™ll also want to check โ€œread:userโ€, which is under โ€œuser.โ€ Then click โ€œGenerate tokenโ€. Save that token (somewhere safe make sure it doesnโ€™t make its way into your repository), and put it in your .env.local file. Now the projects should be able to be read with that token.

More information: https://docs.github.com/en/enterprise-server@3.6/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token.

app/projects/page.tsx

import React from "react";
import { GraphQLClient, gql } from "graphql-request";
import Card from "@/components/Card";
import type { DataProps, Repository } from "@/types";

/**
 *
 * @param
 * @returns displays the list of user's github projects and descriptions
 */

export default async function Projects() {
  const endpoint = "https://api.github.com/graphql";

  if (!process.env.GITHUB_TOKEN) {
    return (
      

Projects

Invalid Github token. Unable to access Github projects.

); } const graphQLClient = new GraphQLClient(endpoint, { headers: { authorization: `Bearer ${process.env.GITHUB_TOKEN}`, }, }); const query = gql` { viewer { login repositories( first: 20 privacy: PUBLIC orderBy: { field: CREATED_AT, direction: DESC } ) { nodes { id name description url primaryLanguage { color id name } forkCount stargazerCount } } } } `; const { viewer: { repositories: { nodes: data }, }, } = await graphQLClient.request(query); return ( <>

Projects

List of GitHub projects

{data.map( ({ id, url, name, description, primaryLanguage, stargazerCount, forkCount, }: Repository) => ( ) )}
); }

This code checks for the github environment variable. If this variable is correct, it then creates a GraphQLClient to access the github api. The graphql query is set up to return the first 20 repositories by id, name, description, url, primary language, forks, and stars. You can adjust this to your needs by changing the query. The results are displayed in a Card component.

Since we have not yet created a navigation menu type localhost://about, localhost://blog, localhost://projects to see your pages.

Header, Navigation Bar, and Theme Changer

Make a directory called _data at the top level. Add the file headerNavLinks.ts to this directory. This file contains names of your directories.

_data/headerNavLinks.ts

const headerNavLinks = [
{ href: "https://dev.to/blog", title: "Blog" },
{ href: "https://dev.to/projects", title: "Projects" },
{ href: "https://dev.to/about", title: "About" },
];


export default headerNavLinks;

Now add:
components/Header.tsx

"use client";
import React, { useEffect, useState } from "react";
import Link from "next/link";
import Navbar from "./Navbar";

const Header = () => {
  // useEffect only runs on the client, so now we can safely show the UI
  const [hasMounted, setHasMounted] = useState(false);

  // When mounted on client, now we can show the UI
  // Avoiding hydration mismatch
  // https://www.npmjs.com/package/next-themes#avoid-hydration-mismatch
  useEffect(() => {
    setHasMounted(true);
  }, []);
  if (!hasMounted) {
    return null;
  }

  return (
    
{/* logo */}
); }; export default Header;

Next is the navigation bar.

app/components/NavBar.tsx

"use client";
import React, { useState } from "react";
import Link from "next/link";
import ThemeChanger from "./ThemeChanger";
import Hamburger from "./Hamburger";
import LetterX from "./LetterX";
import headerNavLinks from "@/data/headerNavLinks";
// names of header links are in
// separate file which allow them to be changed without affecting this component
/**
 *
 * @returns jsx to display the navigation bar
 */
const Navbar = () => {
  const [navShow, setNavShow] = useState(false);

  const onToggleNav = () => {
    setNavShow((status) => {
      if (status) {
        document.body.style.overflow = "auto";
      } else {
        // Prevent scrolling
        document.body.style.overflow = "hidden";
      }
      return !status;
    });
  };

  return (
    
{/* show horizontal nav link medium or greater width */}
{headerNavLinks.map((link) => ( {link.title} ))}
{/* when mobile menu is open move this div to x = 0 when mobile menu is closed, move the element to the right by its own width, effectively pushing it out of the viewport horizontally.*/}
); }; export default Navbar;

Now for the theme change code.
app/components/ThemeChanger.tsx

"use client";
import React, { useEffect, useState } from "react";
import { useTheme } from "next-themes";
import Moon from "./Moon";
import Sun from "./Sun";

/**
 *
 * @returns jsx to switch based on user touching the moon icon
 */
const ThemeChanger = () => {
  const { theme, setTheme } = useTheme();
  const [mounted, setMounted] = useState(false);

  useEffect(() => setMounted(true), []);

  if (!mounted) return null;

  return (
    
{theme === "light" ? ( ) : ( )}
); }; export default ThemeChanger;

Links to the svg components Hamburger, LetterX, Moon, Sun, LetterX :
https://github.com/donnabrown77/developer-blog/blob/main/components/Hamburger.tsx
https://github.com/donnabrown77/developer-blog/blob/main/components/LetterX.tsx
https://github.com/donnabrown77/developer-blog/blob/main/components/Moon.tsx
https://github.com/donnabrown77/developer-blog/blob/main/components/Sun.tsx

Now set up the theme provider which calls next themes.
app/components/Theme-provider.tsx

"use client";

import React from "react";
import { ThemeProvider as NextThemesProvider } from "next-themes";
import { ThemeProviderProps } from "next-themes/dist/types";

// https://github.com/pacocoursey/next-themes/issues/152#issuecomment-1364280564

export function ThemeProvider(props: ThemeProviderProps) {
  return ;
}

app/providers.tsx

"use client";

import React from "react";
import { ThemeProvider as NextThemesProvider } from "next-themes";
import { ThemeProviderProps } from "next-themes/dist/types";

// https://github.com/pacocoursey/next-themes/issues/152#issuecomment-1364280564
// needs to be called NextThemesProvider not ThemesProvider
// not sure why
export function Providers(props: ThemeProviderProps) {
  return ;
}

Modify app/layout.tsx to call the theme provider.

Add these two lines to the top:

import Header from "@/components/Header";
import { Providers } from "./providers";

Wrap the calls to the providers around the call to

{children}. Add the header component before {children}

{children}

In tailwind.config.ts, after plugins[], add:
darkMode: "class",

Run npm dev. You should have everything working except the footer.
For the footer, you can use the social icons here:
https://github.com/donnabrown77/developer-blog/blob/main/components/social-icons/Mail.tsx
I created a social-icons directory under app/components for the icons.
The footer is a component:
https://github.com/donnabrown77/developer-blog/blob/main/components/Footer.tsx

Footer uses a file called siteMetaData.js that you customize for your site.
_data/siteMetData.js

const siteMetadata = {
url: "https://yourwebsite.com",
title: "Next.js Coding Starter Blog",
author: "Your name here",
headerTitle: "Developer Blog",
description: "A blog created with Next.js and Tailwind.css",
language: "en-us",
email: "youremail@email.com",
github: "your github link",
linkedin: "your linkedin",
locale: "en-US",
};


module.exports = siteMetadata;

Now add in app/layout.tsx, like this:


{children}

SEO
Next JS 13 comes with SEO features.

In app/layout.tsx, you can modify the defaults such as this:

export const metadata: Metadata = {
title: "Home",
description: "A developer blog using Next JS 13",
};

For the blog pages, add this to app/posts/[slug]/page.tsx. This uses dynamic information, such as the current route parameters to return a metadata object.

export const generateMetadata = ({ params }: any) => {
const post = allPosts.find(
(post: any) => post._raw.flattenedPath === params.slug
);
return { title: post?.title, excerpt: post?.excerpt };
};

Link to github project:
https://github.com/donnabrown77/developer-blog

Some of the resources I used:
https://nextjs.org/docs/app/building-your-application/routing/colocation
https://darrenwhite.dev/blog/nextjs-tailwindcss-theming
https://nextjs.org/blog/next-13-2#built-in-seo-support-with-new-metadata-api
https://darrenwhite.dev/blog/dark-mode-nextjs-next-themes-tailwind-css
https://claritydev.net/blog/copy-to-clipboard-button-nextjs-mdx-rehype
https://blog.openreplay.com/build-a-mdx-powered-blog-with-contentlayer-and-next/
https://www.sandromaglione.com/techblog/contentlayer-blog-template-with-nextjs
https://jpreagan.com/blog/give-your-blog-superpowers-with-mdx-in-a-next-js-project
https://jpreagan.com/blog/fetch-data-from-the-github-graphql-api-in-next-js
https://dev.to/arshadyaseen/build-a-blog-app-with-new-nextjs-13-app-folder-and-contentlayer-2d6h

Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post
stay-ahead-of-the-game-must-have-front-end-boilerplates-and-starter-kits-for-every-developer

Stay Ahead of the Game Must Have Front-End Boilerplates and Starter Kits for Every Developer

Next Post
how-to-contribute-to-open-source-projects-โ€“-a-handbook-for-beginners

How to Contribute to Open-Source Projects โ€“ A Handbook for Beginners

Related Posts