React NextJS with framer motion and markdown posts

Published: Sep 7, 2023 by Isaac Johnson

My feed kept prompting me to check out a dev.to post about Next.js with React to create smooth Vercel based navbar experience in NodeJS.

I decided to start by reworking that post then filling it out a bit more into rendering a markdown-based blog rendering and functional page stubs.

Setup

Let’s create a directory and see what version of Node we are using presently.

builder@DESKTOP-QADGF36:~/Workspaces$ mkdir nodesite
builder@DESKTOP-QADGF36:~/Workspaces$ cd nodesite/
builder@DESKTOP-QADGF36:~/Workspaces/nodesite$ nvm list
->     v10.22.1
      v12.22.11
       v14.18.1
        v17.6.0
default -> 10.22.1 (-> v10.22.1)
iojs -> N/A (default)
unstable -> N/A (default)
node -> stable (-> v17.6.0) (default)
stable -> 17.6 (-> v17.6.0) (default)
lts/* -> lts/gallium (-> N/A)
lts/argon -> v4.9.1 (-> N/A)
lts/boron -> v6.17.1 (-> N/A)
lts/carbon -> v8.17.0 (-> N/A)
lts/dubnium -> v10.24.1 (-> N/A)
lts/erbium -> v12.22.12 (-> N/A)
lts/fermium -> v14.19.1 (-> N/A)
lts/gallium -> v16.14.2 (-> N/A)

That’s a bit dated. Let’s use latest LTS (at time of writing), 16.14.2.

builder@DESKTOP-QADGF36:~/Workspaces/nodesite$ nvm install 16.14.2
Downloading and installing node v16.14.2...
Downloading https://nodejs.org/dist/v16.14.2/node-v16.14.2-linux-x64.tar.xz...
############################################################################################################################################################################################################################ 100.0%
Computing checksum with sha256sum
Checksums matched!
Now using node v16.14.2 (npm v8.5.0)
builder@DESKTOP-QADGF36:~/Workspaces/nodesite$ nvm use 16.14.2
Now using node v16.14.2 (npm v8.5.0)
builder@DESKTOP-QADGF36:~/Workspaces/nodesite$ node --version
v16.14.2

We’ll be using the newer Plug-N-Play Package Manager, pnpm. It’s a bit more performant than npm.

builder@DESKTOP-QADGF36:~/Workspaces/nodesite$ npm install -g pnpm

added 1 package, and audited 2 packages in 1s

1 package is looking for funding
  run `npm fund` for details

found 0 vulnerabilities
npm notice
npm notice New major version of npm available! 8.5.0 -> 10.0.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v10.0.0
npm notice Run npm install -g npm@10.0.0 to update!
npm notice

We can now install with pnpm create next-app@latest

builder@DESKTOP-QADGF36:~/Workspaces/nodesite$ pnpm create next-app@latest
.../share/pnpm/store/v3/tmp/dlx-12218    |   +1 +
.../share/pnpm/store/v3/tmp/dlx-12218    | Progress: resolved 1, reused 0, downloaded 1, added 1, done
✔ What is your project named? … freshbrewed-app
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import alias? … No / Yes
Creating a new Next.js app in /home/builder/Workspaces/nodesite/freshbrewed-app.

Using pnpm.

Initializing project with template: app-tw


Installing dependencies:
- react
- react-dom
- next
- typescript
- @types/react
- @types/node
- @types/react-dom
- tailwindcss
- postcss
- autoprefixer
- eslint
- eslint-config-next

Downloading registry.npmjs.org/next/13.4.19: 12.97 MB/12.97 MB, done
Downloading registry.npmjs.org/typescript/5.2.2: 7.23 MB/7.23 MB, done
Downloading registry.npmjs.org/@next/swc-linux-x64-musl/13.4.19: 42.84 MB/42.84 MB, done
Downloading registry.npmjs.org/@next/swc-linux-x64-gnu/13.4.19: 36.21 MB/36.21 MB, done
Packages: +325
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Progress: resolved 333, reused 0, downloaded 325, added 325, done

dependencies:
+ @types/node 20.5.8
+ @types/react 18.2.21
+ @types/react-dom 18.2.7
+ autoprefixer 10.4.15
+ eslint 8.48.0
+ eslint-config-next 13.4.19
+ next 13.4.19
+ postcss 8.4.29
+ react 18.2.0
+ react-dom 18.2.0
+ tailwindcss 3.3.3
+ typescript 5.2.2

Done in 7.2s
Initialized a git repository.

Success! Created freshbrewed-app at /home/builder/Workspaces/nodesite/freshbrewed-app

I want to create a nice Navbar akin to this dev.to blog so I’ll add framer-motion

builder@DESKTOP-QADGF36:~/Workspaces/nodesite$ pnpm i framer-motion
Packages: +4
++++
Progress: resolved 4, reused 1, downloaded 3, added 4, done

dependencies:
+ framer-motion 10.16.2

Done in 1s

If we want to test first, we need to hop into the app directory and git it a go

builder@DESKTOP-QADGF36:~/Workspaces/nodesite$ cd freshbrewed-app/
builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ pnpm dev

> freshbrewed-app@0.1.0 dev /home/builder/Workspaces/nodesite/freshbrewed-app
> next dev

- ready started server on [::]:3000, url: http://localhost:3000
Attention: Next.js now collects completely anonymous telemetry regarding usage.
This information is used to shape Next.js' roadmap and prioritize features.
You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL:
https://nextjs.org/telemetry

- event compiled client and server successfully in 185 ms (20 modules)
- wait compiling...
- event compiled client and server successfully in 133 ms (20 modules)

I was having some WSL issues when writing, so I ended up firing up Firefox into X using Windows 11 so I could properly hit localhost:3000

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ pnpm dev

> freshbrewed-app@0.1.0 dev /home/builder/Workspaces/nodesite/freshbrewed-app
> next dev

- ready started server on [::]:3000, url: http://localhost:3000
loc- event compiled client and server successfully in 294 ms (20 modules)
al- wait compiling...
- event compiled client and server successfully in 118 ms (20 modules)
- wait compiling /page (client and server)...
- event compiled client and server successfully in 3.3s (426 modules)
- wait compiling...
- event compiled successfully in 257 ms (235 modules)
- wait compiling /favicon.ico/route (client and server)...
- event compiled client and server successfully in 2.7s (472 modules)

/content/images/2023/09/nextapp-01.png

I’ll create a Navbar.tsx component which will have my main headings/routes.

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ mkdir -p src/app/components
builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ vi src/app/components/Navbar.tsx
builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ cat src/app/components/Navbar.tsx
"use client";

import { motion } from "framer-motion";
import { useState } from "react";
import { usePathname } from "next/navigation";
import Link from "next/link";

const navItems = [
  {
    path: "/",
    name: "Home",
  },
  {
    path: "/fun",
    name: "Fun",
  },
  {
    path: "/guestbook",
    name: "Guestbook",
  },
  {
    path: "/blog",
    name: "Blog",
  },
];

export default function NavBar() {
  let pathname = usePathname() || "/";

  if (pathname.includes("/blog/")) {
    pathname = "/blog";
  }

  const [hoveredPath, setHoveredPath] = useState(pathname);

  return (
    <div className="border border-stone-800/90 p-[0.4rem] rounded-lg mb-12 sticky top-4 z-[100] bg-stone-900/80 backdrop-blur-md">
      <nav className="flex gap-2 relative justify-start w-full z-[100]  rounded-lg">
        {navItems.map((item, index) => {
          const isActive = item.path === pathname;

          return (
            <Link
              key={item.path}
              className={`px-4 py-2 rounded-md text-sm lg:text-base relative no-underline duration-300 ease-in ${
                isActive ? "text-zinc-100" : "text-zinc-400"
              }`}
              data-active={isActive}
              href={item.path}
              onMouseOver={() => setHoveredPath(item.path)}
              onMouseLeave={() => setHoveredPath(pathname)}
            >
              <span>{item.name}</span>
              {item.path === hoveredPath && (
                <motion.div
                  className="absolute bottom-0 left-0 h-full bg-stone-800/80 rounded-md -z-10"
                  layoutId="navbar"
                  aria-hidden="true"
                  style={{
                    width: "100%",
                  }}
                  transition={{
                    type: "spring",
                    bounce: 0.25,
                    stiffness: 130,
                    damping: 9,
                    duration: 0.3,
                  }}
                />
              )}
            </Link>
          );
        })}
      </nav>
    </div>
  );
}

We now need to customize the layout.tsx.

$ cat src/app/layout.tsx
import "./globals.css";
import NavBar from "@/components/navbar";

export const metadata = {
  title: "Create NextJS App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className="bg-gradient-to-tr overflow-x-hidden min-w-screen from-zinc-950 via-stone-900 to-neutral-950 flex min-h-screen flex-col items-center justify-between">
        <main className="p-4 py-24 gap-6 w-full lg:w-[55%]">
          <section className="w-full flex gap-4 justify-start mb-6 p-2">
            <div>
              <img src="https://avatars.githubusercontent.com/u/68690233?s=100&v=4" alt="avatar"
                className="w-12 h-12 rounded-full shadow-lg grayscale hover:grayscale-0 duration-300"
              />
            </div>
            <div className="flex flex-col gap-2 justify-center">
              <h2 className="mb-0 text-zinc-100 font-bold">Isaac</h2>
              <p className="mb-0 text-zinc-400 font-semibold leading-none">
                Blogger, Father and Adventurer
              </p>
            </div>
          </section>
          <NavBar />
          {children}
        </main>
      </body>
    </html>
  );
}

It’s mostly to build out the body. Here is a diff of what was there:

$ diff src/app/layout.tsx.bak src/app/layout.tsx
1,3c1,2
< import './globals.css'
< import type { Metadata } from 'next'
< import { Inter } from 'next/font/google'
---
> import "./globals.css";
> import NavBar from "@/components/navbar";
5,10c4,7
< const inter = Inter({ subsets: ['latin'] })
<
< export const metadata: Metadata = {
<   title: 'Create Next App',
<   description: 'Generated by create next app',
< }
---
> export const metadata = {
>   title: "Create NextJS App",
>   description: "Generated by create next app",
> };
15c12
<   children: React.ReactNode
---
>   children: React.ReactNode;
19c16,34
<       <body className={inter.className}>{children}</body>
---
>       <body className="bg-gradient-to-tr overflow-x-hidden min-w-screen from-zinc-950 via-stone-900 to-neutral-950 flex min-h-screen flex-col items-center justify-between">
>         <main className="p-4 py-24 gap-6 w-full lg:w-[55%]">
>           <section className="w-full flex gap-4 justify-start mb-6 p-2">
>             <div>
>               <img src="https://avatars.githubusercontent.com/u/68690233?s=100&v=4" alt="avatar"
>                 className="w-12 h-12 rounded-full shadow-lg grayscale hover:grayscale-0 duration-300"
>               />
>             </div>
>             <div className="flex flex-col gap-2 justify-center">
>               <h2 className="mb-0 text-zinc-100 font-bold">Isaac</h2>
>               <p className="mb-0 text-zinc-400 font-semibold leading-none">
>                 Blogger, Father and Adventurer
>               </p>
>             </div>
>           </section>
>           <NavBar />
>           {children}
>         </main>
>       </body>
21c36
<   )
---
>   );

Lastly, we should scrub up the globals.css so it doesn’t mess with our styles. I’ll just sed out all but the first 4 lines.

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ cat src/app/globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
  --foreground-rgb: 0, 0, 0;
  --background-start-rgb: 214, 219, 220;
  --background-end-rgb: 255, 255, 255;
}

@media (prefers-color-scheme: dark) {
  :root {
    --foreground-rgb: 255, 255, 255;
    --background-start-rgb: 0, 0, 0;
    --background-end-rgb: 0, 0, 0;
  }
}

body {
  color: rgb(var(--foreground-rgb));
  background: linear-gradient(
      to bottom,
      transparent,
      rgb(var(--background-end-rgb))
    )
    rgb(var(--background-start-rgb));
}
builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ sed -i '1,4!d' src/app/globals.css
builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ cat src/app/globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;

My first issue was that NavBar wasn’t found.

/content/images/2023/09/nextapp-02.png

I should have run build first which would have shown that.

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ pnpm build

> freshbrewed-app@0.1.0 build /home/builder/Workspaces/nodesite/freshbrewed-app
> next build

Failed to compile.

./src/app/layout.tsx
Module not found: Can't resolve '@/components/navbar'

https://nextjs.org/docs/messages/module-not-found


> Build failed because of webpack errors
- info Creating an optimized production build . ELIFECYCLE  Command failed with exit code 1.
builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$

It took some experimenting to figure out why the pathing was off. I knew we rooted in /src from the setting in tsconfig

/content/images/2023/09/nextapp-04.png

But it seems I needed to add ‘app’ to the path

/content/images/2023/09/nextapp-03.png

Then it built without issue:

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ pnpm build

> freshbrewed-app@0.1.0 build /home/builder/Workspaces/nodesite/freshbrewed-app
> next build

- info Creating an optimized production build
- info Compiled successfully

./src/app/layout.tsx
20:15  Warning: Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element  @next/next/no-img-element

info  - Need to disable some ESLint rules? Learn more here: https://nextjs.org/docs/basic-features/eslint#disabling-rules
- info Linting and checking validity of types
- info Collecting page data
[    ] - info Generating static pages (0/4)(node:3273) ExperimentalWarning: buffer.Blob is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
- info Generating static pages (4/4)
- info Finalizing page optimization

Route (app)                                Size     First Load JS
┌ ○ /                                      5.17 kB        83.6 kB
└ ○ /favicon.ico                           0 B                0 B
+ First Load JS shared by all              78.5 kB
  ├ chunks/431-82c16d9f92a293e1.js         26.1 kB
  ├ chunks/e0f3dcf3-c6e4db7d5451056b.js    50.5 kB
  ├ chunks/main-app-72ba40e3bddb704a.js    221 B
  └ chunks/webpack-a4eed088dcf0360e.js     1.67 kB

Route (pages)                              Size     First Load JS
─ ○ /404                                   181 B          76.5 kB
+ First Load JS shared by all              76.3 kB
  ├ chunks/framework-510ec8ffd65e1d01.js   45.1 kB
  ├ chunks/main-615196ad320d4c32.js        29.4 kB
  ├ chunks/pages/_app-991576dc368ea245.js  195 B
  └ chunks/webpack-a4eed088dcf0360e.js     1.67 kB

○  (Static)  automatically rendered as static HTML (uses no initial props)

We can now see the nav in action:

Creating a repo

It’s at this point I have a basic framework and want to save it aside.

I’ll use gitignore.io to create a gitignore file.

I ended up appending to the one there already. It was then I realized the whole create-react pnpm actually setup git for me. I did not expect that.

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ git status
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   .gitignore
        modified:   src/app/globals.css
        modified:   src/app/layout.tsx

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        src/app/components/

no changes added to commit (use "git add" and/or "git commit -a")
builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ git branch
* main
builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ git remote show origin
fatal: 'origin' does not appear to be a git repository
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ git remote show
builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$

I can add and commit, but without a remote, I can’t push anywhere

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ git add -A
builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ git commit -m "phase 1"
[main 8320e2d] phase 1
 4 files changed, 291 insertions(+), 49 deletions(-)
 create mode 100644 src/app/components/Navbar.tsx
 rewrite src/app/globals.css (88%)
 rewrite src/app/layout.tsx (76%)
builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ git push
fatal: No configured push destination.
Either specify the URL from the command-line or configure a remote repository using

    git remote add <name> <url>

and then push using the remote name

    git push <name>

Azure Repo

I debated between gitea, gitlab and github. Since it’s pretty simple, I decided to just make a public Github repo and push it there.

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ git remote add origin https://github.com/idjohnson/nodesite.git
builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ git push -u origin main
Enumerating objects: 29, done.
Counting objects: 100% (29/29), done.
Delta compression using up to 16 threads
Compressing objects: 100% (25/25), done.
Writing objects: 100% (29/29), 49.71 KiB | 9.94 MiB/s, done.
Total 29 (delta 3), reused 0 (delta 0)
remote: Resolving deltas: 100% (3/3), done.
To https://github.com/idjohnson/nodesite.git
 * [new branch]      main -> main
Branch 'main' set up to track remote branch 'main' from 'origin'.

Stubbed pages

My next step was to make a few stubbed in pages.

/content/images/2023/09/nextapp-05.png

import "../globals.css";
import NavBar from "@/app/components/Navbar";

export const metadata = {
  title: "Create NextJS App",
  description: "Generated by create next app",
};

// <!-- use http://caius.github.io/github_id/ -->
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
            <div className="flex flex-col gap-2 justify-center">
              <h2 className="mb-0 text-zinc-100 font-bold">and</h2>
              <p className="mb-0 text-zinc-400 font-semibold leading-none">
                .... blog here .....
              </p>
            </div>
  );
}

Which gives us a basic layout without crashing (checked in as phase 2)

Blog

Let’s pull in Markdown to render as blog entries.

I need to install some libraries such as react icons

pnpm install --save gray-matter
pnpm install --save markdown-to-jsx
pnpm install --save react-icons

Then create a lot of files. You can see the SHA here

/content/images/2023/09/nextapp-06.png

builder@DESKTOP-QADGF36:~/Workspaces/nodesite/freshbrewed-app$ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   package.json
        modified:   pnpm-lock.yaml
        modified:   src/app/blog/page.tsx

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        content/
        src/app/blog/[slug]/
        src/app/components/common/
        src/app/components/preview/
        src/app/content/
        src/app/libs/
        src/app/providers/
        src/app/types/
        src/content/

Let’s see it all in action:

Summary

I started this off by following this Ashish dev.to post, with some tweaks and fixes. I liked the nav, but it was mostly broken when anything was clicked.

I then added in some stub pages and then used this Rahil Siddique repo as a basis to figure out how to work out the blogging using markdown. Though my end code is a bit different than his.

I wanted to have something that was a functional base others can use to do a general React website. This post will be followed up with a bit more, like the guestbook, before we then move on to containerizing and hosting or rendering out - the deployment parts I have yet to formulate fully.

Next NodeJS React Vercel

Have something to add? Feedback? You can use the feedback form

Isaac Johnson

Isaac Johnson

Cloud Solutions Architect

Isaac is a CSA and DevOps engineer who focuses on cloud migrations and devops processes. He also is a dad to three wonderful daughters (hence the references to Princess King sprinkled throughout the blog).

Theme built by C.S. Rhymes