Build a Blog with NextJs + GraphCMS

SirenaAlyce
SirenaAlyce

Created At: December 26th, 2021

Updated At: March 20th, 2022

Hey! A great way to showcase and practice developer skills is to create your own portfolio from scratch. Especially for self-taught programmers and newbies with a good grasp of the basics. Yes, starting from scratch takes longer than using a platform like WordPress or Wix. However, you will learn a great deal during the process. That’s the name of the game, learn, show, and grow. This post will teach you how to build a blog to add to your portfolio using NextJS and GraphCMS.

Check out my full tutorial here:

Here is the source code:

/components/NavBar.js

import Link from 'next/link';

export default function NavBar() {
    return (
        <div className='w-screen grid grid-cols-2 p-2'>
           <div className='text-pink-600 font-extrabold'>SirenaAlyce</div> 
           <div className='ml-auto'>
               <ul className='grid grid-cols-3 text-center'>
                   <li>
                       <Link href='/'>
                           <a>Home</a>
                       </Link>
                   </li>
                   <li>
                       <Link href='/projects'>
                           <a>My Work</a>
                       </Link>
                   </li>
                   <li>
                       <Link href='/blog'>
                           <a>Blog</a>
                       </Link>
                   </li>
               </ul>
           </div>
        </div>
    )
}
⁠

/components/Post.module.css

.post p {
    @apply my-2 text-sm
}

.post h1 {
    @apply my-2 text-5xl text-pink-600 font-extrabold
}

.post h2 {
    @apply my-2 text-3xl text-pink-600 font-extrabold
}

.post ul {
    @apply my-2 text-sm
}

/pages/post/[slug].js

import NavBar from "../../components/NavBar";
import { GraphQLClient, gql } from "graphql-request";
import style from '../../components/Post.module.css'

const graphcms = new GraphQLClient(
  "<insert your private/public GraphCMS API key>"
);

const QUERY = gql`
  query Post($slug: String!) {
    post(where: { slug: $slug }) {
      id
      slug
      title
      author {
        name
      }
      content {
        html
      }
    }
  }
`;

export const SLUGLIST = gql`
  {
    posts {
      slug
    }
  }
`;

export async function getStaticProps({ params }) {
  const slug = params.slug;

  const data = await graphcms.request(QUERY, { slug });

  const post = data.post;

  return {
    props: {
      post,
    },
  };
}

export async function getStaticPaths() {
  const { posts } = await graphcms.request(SLUGLIST);

  return {
    paths: posts.map((post) => ({ params: { slug: post.slug } })),
    fallback: "blocking",
  };
}

export default function Post({ post }) {
  return (
    <div className="w-screen h-full text-white">
      <NavBar />
      <div className="m-8">
      <div>
        <h2 className="text-pink-600 font-extrabold text-9xl">{post.title}</h2>
      </div>
        <p className="text-xs">{post.author.name}</p>
        <div className={`post ${style.post}`} dangerouslySetInnerHTML={{ __html: post.content.html }} />
      </div>
    </div>
  );
}

/pages/project/[slug].js

import NavBar from "../../components/NavBar";
import { GraphQLClient, gql } from "graphql-request";
import Image from "next/image";
import Link from "next/link";

const graphcms = new GraphQLClient(
  "<insert your private/public GraphCMS API key>"
);

const QUERY = gql`
  query Project($slug: String!) {
    project(where: { slug: $slug }) {
      id
      slug
      title
      description
      projectImage {
        url
      }
      demoUrl
      sourceUrl
    }
  }
`;

export const SLUGLIST = gql`
  {
    projects {
      slug
    }
  }
`;

export async function getStaticProps({ params }) {
  const slug = params.slug;

  const data = await graphcms.request(QUERY, { slug });

  const project = data.project;

  return {
    props: {
      project,
    },
  };
}

export async function getStaticPaths() {
  const { projects } = await graphcms.request(SLUGLIST);

  return {
    paths: projects.map((project) => ({ params: { slug: project.slug } })),
    fallback: "blocking",
  };
}

export default function Project({ project }) {
  return (
    <div className="w-screen h-full text-white">
      <NavBar />
      <div className="m-8">
        <div>
          <h2 className="text-pink-600 font-extrabold text-9xl">{project.title}</h2>
        </div>
        <Image
          src={project.projectImage.url}
          alt="project image"
          width={300}
          height={300}
        />
        <p>{project.description}</p>
        <Link href={project.demoUrl}>
          <a className="hover:text-pink-600 pr-2 font-bold">DEMO</a>
        </Link>
        <Link href={project.sourceUrl}>
          <a className="hover:text-pink-600 pr-2 font-bold">SOURCE</a>
        </Link>
      </div>
    </div>
  );
}

/pages/blog.js

import NavBar from "../components/NavBar";
import { GraphQLClient, gql } from "graphql-request";
import Link from "next/link";
import Image from "next/image";

const graphcms = new GraphQLClient(
  "<insert your private/public GraphCMS API key>"
);

const QUERY = gql`
  {
    posts {
      id
      slug
      title
      coverPhoto {
        url
      }
    }
  }
`;

export async function getStaticProps() {
  const { posts } = await graphcms.request(QUERY);

  return {
    props: {
      posts,
    },
  };
}

export default function Blog({ posts }) {
  return (
    <div className="w-screen h-full text-white">
      <NavBar />
      <div className="grid p-4 border-2 border-pink-600 h-56 content-center">
        <h2 className="text-black bg-pink-600 text-9xl font-extrabold">Blog</h2>
      </div>
      <div className="grid place-content-center m-4">
        {posts.map(({ id, title, slug, coverPhoto }) => (
          <div key={id}>
            <div className="grid grid-cols-2 items-center">
              <div>
                <Image
                  src={coverPhoto.url}
                  alt="blog post cover image"
                  width={150}
                  height={150}
                />
              </div>
              <div>
                <h2 className="text-pink-600 text-3xl font-bold">
                  <Link href={`/post/${slug}`}>
                    <a>{title}</a>
                  </Link>
                </h2>
              </div>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

/pages/index.js

import NavBar from "../components/NavBar";
import { GraphQLClient, gql } from "graphql-request";
import Link from "next/link";
import Image from "next/image";

const graphcms = new GraphQLClient(
  "<insert your private/public GraphCMS API key>"
);

const QUERY = gql`
  {
    posts {
      id
      title
      slug
      coverPhoto {
        url
      }
    }
    projects {
      id
      title
      slug
      projectImage {
        url
      }
    }
  }
`;

export async function getStaticProps() {
  const { posts, projects } = await graphcms.request(QUERY);
  
  return {
    props: {
      posts,
      projects
    }
  }
}

export default function Home({ posts, projects}) {
  return (
    <div className="w-screen h-full text-white">
      <NavBar />
      <div className="p-4 h-full">
        <div className="grid grid-cols-1 sm:grid-cols-2 items-center h-96">
          <div>
            <h2 className="text-pink-600 text-2xl font-extrabold">Hey! I'm Sirena Alyce!</h2>
          </div>
          <div>
            <p>
              I'm a front-end developer in Atlanta, GA. Powder gummi bears
              soufflé donut jelly-o pudding pie liquorice oat cake. Lemon drops
              powder lemon drops brownie dragée sugar plum. Tiramisu jelly beans
              pudding cupcake ice cream marzipan ice cream pie.
            </p>
          </div>
        </div>
        <div className="mb-4">
          <div>
            <h2 className="text-black bg-pink-600 text-2xl p-1 mb-2 font-extrabold">About</h2>
          </div>
          <div>
            <p>
              Liquorice candy canes candy croissant sweet cake halvah. Donut
              bear claw lollipop sweet fruitcake pudding candy croissant.
              Jujubes jelly lemon drops chocolate bar muffin jelly icing. Tart
              gummi bears croissant danish lemon drops pudding powder chocolate
              cake sweet roll. Pie caramels bear claw shortbread pastry gummies.
              Cake cookie biscuit bear claw sugar plum cookie halvah. Sesame
              snaps candy gummies marshmallow liquorice topping. Powder gummi
              bears cotton candy gingerbread soufflé sesame snaps apple pie
              dessert cheesecake. Cookie cotton candy chocolate toffee tiramisu
              chocolate bonbon liquorice.
            </p>
          </div>
        </div>
        <div>
          <div>
            <h2 className="text-black bg-pink-600 text-2xl p-1 mb-2 font-extrabold">Projects</h2>
          </div>
          <div className="grid grid-cols-1 sm:grid-cols-3 h-full place-items-center mb-4">
            {projects.slice(0,3).map(({ id, title, slug, projectImage }) => (
              <div key={id} className="rounded grid grid-cols-1 place-items-center shadow-md shadow-white hover:shadow-pink-300 my-2 p-3 bg-gray-900">
                <h2>
                  <Link href={`/project/${slug}`}>
                    <a>{title}</a>
                  </Link>
                </h2>
                <Image src={projectImage.url} width={150} height={150} />
              </div>
            ))}
          </div>
        </div>
        <div>
          <div>
            <h2 className="text-black bg-pink-600 text-2xl p-1 mb-2 font-extrabold">Latest Articles</h2>
          </div>
          <div className="grid grid-cols-2">
            {posts.slice(0,2).map(({ id, title, slug }) => (
              <div key={id}>
                <h2 className="hover:text-pink-600 text-2xl font-bold">
                  <Link href={`/post/${slug}`}>
                    <a>{title}</a>
                  </Link>
                </h2>
              </div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
}

/pages/projects.js

import NavBar from "../components/NavBar";
import { GraphQLClient, gql } from "graphql-request";
import Link from "next/link";
import Image from "next/image";

const graphcms = new GraphQLClient(
  "<insert your private/public GraphCMS API key>"
);

const QUERY = gql`
  {
    projects {
      id
      slug
      title
      projectImage {
        url
      }
    }
  }
`;

export async function getStaticProps() {
  const { projects } = await graphcms.request(QUERY);

  return {
    props: {
      projects,
    },
  };
}

export default function Projects({ projects }) {
  return (
    <div className="w-screen h-full text-white">
      <NavBar />
      <div className="grid p-4 border-2 border-pink-600 h-56 content-center">
        <h2 className="text-black bg-pink-600 text-9xl font-extrabold">
          Projects
        </h2>
      </div>
      <div className="grid place-content-center m-4">
        {projects.map(({ id, title, slug, projectImage }) => (
          <div key={id}>
            <div className="grid grid-cols-2 items-center">
              <div>
                <Image
                  src={projectImage.url}
                  alt="blog project cover image"
                  width={150}
                  height={150}
                />
              </div>
              <div>
                <h2 className="text-pink-600 text-3xl font-bold">
                  <Link href={`/project/${slug}`}>
                    <a>{title}</a>
                  </Link>
                </h2>
              </div>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

⁠globals.css

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
    html {
        @apply bg-black
    }
}

next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  images: {
    domains: ['media.graphcms.com'],
  },
}

module.exports = nextConfig

tailwind.config.js

module.exports = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}


Let's Connect!

Be sure to subscribe to my YouTube Channel!

Get social with me on IG @sirenaalyce.io.

Be inspired.

Be encouraged.

Be confident.

Back to Blog Home
© 2021 Sirena Alyce, LLC.