跪拜 Guibai
← Back to the summary

A File-by-File Walkthrough of a Next.js + Redis Markdown Note App

Reading a Next.js Full-Stack Note Application from Scratch

This article is based on a next-blog note project and explains its technical background, directory structure, route design, component breakdown, and data services file by file.


1. Technical Background: Why Next.js

The README starts by explaining why this project chose Next.js and the key concepts behind it.

1.1 The Role of npx

npx is a tool built into npm that can run Node packages directly without needing to install dependencies globally.
npx = npm i -g create-next-app + create-next-app

1.2 Why Next.js (React Full-Stack Scaffold)

Next.js is a React full-stack development scaffold. The README lists several key concepts:

Concept Meaning
SSR Server-Side Rendering
SEO Search Engine Optimization
RSC React Server Component

These concepts will appear repeatedly in the code later — for example, whether a component includes "use client" determines if it is a server component or a client component.


2. Project Requirements: A Markdown Note System

The README clearly states what this project aims to do:

Specific functional points broken down as follows:

  1. Two-column interface: Note list on the left, note content on the right (corresponding to page.js at /).
  2. Click new to add a note, after which the left list updates synchronously.
  3. Edit functionality, allowing deletion of a note, with the left list updating synchronously.
  4. Edit the current note, supporting markdown.
  5. Search functionality.

The routes also correspond to these requirements (App Router file-based routing):

/add          POST create
/note/[id]    Dynamic route, page.js is the note detail
/note/[id]/edit   Edit
/edit         page for creating a new one

3. Directory Structure

The project uses Next.js's convention-based directory structure:

- app          Main page directory
    page.js    Home page
    layout.js  Layout
    [id]       Dynamic route
- components   Components
- lib          Database operations, utility functions
- public       Static resources (static server)

Core convention: Data business logic goes in the lib directory, components in components, and pages in app.


4. Configuring alias: @ Points to Root

In app/notes/[id]/page.js, when importing lib/redis.js, using a relative path would require writing ../../../lib/redis.js, which is cumbersome. So an alias shortcut was configured:

baseUrl: .
path:
  @/components/*
  @/lib/*

After configuration, @ directly points to the root directory, allowing you to write:

import Sidebar from '@/components/Sidebar'
import { getAllNotes } from '@/lib/redis'

This is already used in the actual code (app/layout.js, components/Sidebar.js, SidebarNoteItem.js).


5. Layout: app/layout.js

layout.js is the root layout of the page, and the code itself is an async component:

import './style.css'
import Sidebar from '@/components/Sidebar'

export default async function RootLayout({ children }) {
  return (
    <html>
      <head>
        <title>HHGZ's Blog</title>
        <meta name="description" content="..." />
        <meta name="keywords" content="llm,claude,deepseek,rag,langchain" />
      </head>
      <body>
        <div className="container">
          <div className="main">
            <Sidebar/>
            <section className="col note-viewer">{children}</section>
          </div>
        </div>
      </body>
    </html>
  )
}

From this file, several knowledge points can be observed:

  1. Layout hierarchy (organized in the README):

    layout
    └── html
        ├── head
        │   ├── title
        │   └── meta
        └── body
            ├── nav       Sidebar, navigation bar
            └── children  page.js
    
  2. Two-column layout: Left side <Sidebar/> (note list), right side <section className="col note-viewer">{children}</section> (note content), exactly matching the requirement for "left and right columns."

  3. children is the child page: The {children} position will render the corresponding page.js.


6. Home Page Placeholder: app/page.js

// RSC component async asynchronous to await fetching backend data first
export default async function Page() {
  return (
    <div className="note--empty-state">
      <span className="note-text--empty-state">
        Click a note on the left to view something.
      </span>
    </div>
  )
}

The comment highlights a key point: RSC components can be written as async because they need to await fetching data from the backend first. This home page hasn't written the data fetching logic yet, just returning an empty state prompt.


7. Component Breakdown: Specification-Driven Programming + BEF

7.1 Specification-Driven Programming

The README emphasizes an important working method — don't rush to write code before development, but first:

  1. Analyze requirements
  2. Determine the technical solution (Next.js)
  3. Break down task details: routes + components
  4. Plan which components are needed

Among these, "components are work units, AI-generated work units" — break large tasks into small components, then have AI generate them one by one.

The planned component tree for the project:

Sidebar
  SidebarSearchField
  EditButton (reusable)
  SidebarNoteList
    NoteItem
Note
  NoteEditor   Edit
  NotePreview  Responsible for the note preview interface

7.2 BEF Naming Convention

Paired with the atomic CSS framework Tailwind CSS, the project adopts the BEF naming convention (Block / Element / Modifier):

Corresponding to class naming in the code, for example note--empty-state, note-text--empty-state, all follow this convention for easier maintenance.

7.3 The "to be continue" Comment Technique

The README mentions writing comments in the code to plan future tasks (to be continue), which is beneficial for team collaboration, memory, and maintenance — writing "things to do" in comments. Such placeholder comments can be seen in Sidebar.js:

<section className="sidebar-menu" role="menubar">
{/*SidebarMenu*/}
</section>

And comments explaining block semantics:

{/* sidebar
Block: e-commerce site, product introduction, reviews, images, price...
Semantically, it is an independent content area, a slideshow */}

8. Sidebar Component File-by-File Breakdown

8.1 Sidebar.js — Sidebar Container

export default async function Sidebar() {
  const notes = await getAllNotes();
  return (
    <section className="col sidebar">
      <Link href="/" className="sidebar-header">
        <img className="logo" src="/logo.svg" width="22px" height="20px" role="presentation" />
        <strong>LLM Notes</strong>
      </Link>
      <section className="sidebar-menu" role="menubar">
        {/*SidebarMenu*/}
      </section>
      <nav>
        <SidebarNoteList notes={notes} />
      </nav>
    </section>
  )
}

Knowledge points:

8.2 SidebarNoteList.js — Note List

export default async function SidebarNoteList({ notes }) {
  const arr = Object.entries(notes); // Convert hash to 2D array for easy component mapping
  if (arr.length == 0) {
    return <div className="notes-empty">No Notes created yet!</div>
  }
  return (
    <ul className="notes-list">
      {
        arr.map(([noteId, note]) => (
          <li key={noteId}>
            <SidebarNoteItem noteId={noteId} note={JSON.parse(note)} />
          </li>
        ))
      }
    </ul>
  )
}

Key points:

8.3 SidebarNoteItem.js — Single Note

export default function SidebarNoteItem({ noteId, note }) {
  const { title, content = '', updateTime } = note;
  return (
    <SidebarNoteItemContent
      id={noteId}
      title={note.title}
      expandChildren={
        <p className="sidebar-note-excerpt">
          {content.substring(0, 20) || <i>(No content)</i>}
        </p>
      }
    >
      <header className="sidebar-note-header">
        <strong>{title}</strong>
        <small>{dayjs(updateTime).format('YYYY-MM-DD')}</small>
      </header>
    </SidebarNoteItemContent>
  )
}

Knowledge points:

8.4 SidebarNoteItemContent.js — Client Component

"use client";
import { useState, useEffect } from 'react';
export default function SidebarNoteItemContent({ id, title, children, expandChildren }) {
  return (
    <>
      {children}
    </>
  )
}

Knowledge points:

8.5 SidebarNoteList2.js — Comparison: Inline Approach

The project also has a SidebarNoteList2.js, which does not extract a single note into a component but renders inline directly within map:

arr.map(([noteId, note]) => {
  const { title, updateTime } = JSON.parse(note);
  return (
    <li key={noteId}>
      <header className="sidebar-note-header">
        <strong>{title}</strong>
        <small>{dayjs(updateTime).format('YYYY-MM-DD HH:mm:ss')}</small>
      </header>
    </li>
  )
})

Comparing with SidebarNoteList.js, the differences between the two precisely illustrate the distinction between "component extraction vs inline":

SidebarNoteList.js SidebarNoteList2.js
Single item structure Extracted into SidebarNoteItem component Directly inline within map
Excerpt field Has content excerpt No excerpt, only title
Time format YYYY-MM-DD YYYY-MM-DD HH:mm:ss

The version with extracted components is more conducive to reuse and future expansion, while the inline version is simpler and more direct.


9. Data Service: lib/redis.js (Redis)

9.1 Why Redis

The README introduces that this project's data service chose Redis:

Its "advanced aspect" lies in: optimized storage methods and corresponding methods for different types of values:

Data Type Methods
String get / set
Hash hget / hset

Typical uses: caching, counters, leaderboards.

9.2 Redis + MySQL Caching Scenario

The README gives a very intuitive example using "Juejin homepage article list":

This solves the "I/O bottleneck of readable/writable data," using Redis as a cache in front of MySQL.

9.3 lib/redis.js Code

// Node Redis client, driver
import Redis from 'ioredis';
const redis = new Redis(); // Default NOSQL

// Hash key string ID, value is serialized string of note
const initialData = {
  "1702459181837": '{"title":"sunt aut",...}',
  "1702459182837": '{"title":"qui est",...}',
  "1702459188837": '{"title":"ea molestias",...}'
}

export async function getAllNotes() {
  // Hash data type
  const data = await redis.hgetall('notes');
  if (Object.keys(data).length == 0) {
    await redis.hmset("notes", initialData);
  }
  return await redis.hgetall('notes');
}

Knowledge points:

This function is exactly the data source called by await getAllNotes() in Sidebar.js earlier, also confirming that "the lib directory holds Next.js data business logic."

9.4 About Interfaces: RPC

The end of the README also mentions /app/api/route.jsRPC remote calls for interfaces (this file has not been implemented in the current project, belonging to the part to be supplemented).


10. Summary

Project Summary

This is a markdown note application based on Next.js + Redis, with a clear overall architecture:

Knowledge Point Summary

  1. npx: A tool built into npm, can run Node packages without global installation, npx create-next-app for quick scaffolding.
  2. Next.js Core Concepts: SSR (Server-Side Rendering), SEO, RSC (React Server Components), use client client components, hydration.
  3. App Router File-Based Routing: /add POST, /note/[id] dynamic route, /edit, etc.
  4. async Components: RSC components written as async can await fetching data from the backend first.
  5. Specification-Driven Programming: Analyze requirements and break down tasks (routes + components) before development; components are work units.
  6. BEF Naming Convention: Block / Element (_) / Modifier (__), paired with Tailwind CSS atomic classes.
  7. Object.entries: Converts a hash object into a 2D array, convenient for map rendering lists.
  8. JSON.parse: Redis stores strings, which must be parsed into objects when retrieved.
  9. dayjs: Date formatting library, format('YYYY-MM-DD'), etc.
  10. ioredis + Redis: NOSQL in-memory database (port 6379), hash type uses hgetall / hmset, can be used for caching, counters, leaderboards, commonly used in Redis + MySQL caching scenarios.
  11. alias Configuration: @/components/*, @/lib/*, @ points directly to the root directory.
  12. "to be continue" Comment Technique: Using comments to plan pending tasks, beneficial for collaboration and maintenance.

This project may be small, but it has all the essential parts: from scaffolding, routing, and component breakdown to data services and cache design, it forms a complete learning path for Next.js full-stack development.