Building a Frontend Knowledge Base with Next.js, Tailwind CSS, and Notion Integration

This article walks through creating a full‑stack frontend knowledge‑base project using Next.js with Tailwind CSS, explains the benefits of server‑side rendering, demonstrates routing, component usage, Notion API integration, markdown rendering, and deployment to Vercel, providing complete code snippets and configuration steps.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Building a Frontend Knowledge Base with Next.js, Tailwind CSS, and Notion Integration

The author created a full‑stack frontend knowledge‑base to consolidate scattered frontend concepts, making future lookup and learning faster. All source code is open‑source on GitHub.

Why Next.js

Next.js is a React‑based framework for production‑grade server‑side rendering (SSR) and static site generation (SSG).

Key advantages of Next.js include:

SSR and SSG : Pages are rendered on the server, improving load speed and SEO.

Fast loading : Pre‑rendering reduces client‑side work.

Hot Module Replacement (HMR) : Changes appear instantly without a full refresh.

Simple deployment : Deploy to Vercel, Netlify, or custom servers with minimal configuration.

Rich ecosystem : Large community and many plugins.

Automatic optimization : Built‑in compression and caching.

Data fetching methods : getServerSideProps and getStaticProps simplify data handling.

Project Initialization

The project uses Node 16.x and is created with: npx create-next-app frontend-knowledge No TypeScript and no src directory are used. Tailwind CSS is bundled with PostCSS and autoprefixer.

App Directory Structure

The app folder defines routes. Files like layout.tsx and page.tsx correspond to the root URL /. Each sub‑folder maps to a URL.

API routes are placed under pages/api.

Frontend Pages

App Routing

Every file inside app becomes a route. The root page is accessed at http://localhost:3000/ and renders app/page.jsx.

The shared layout component ( Layout.jsx) wraps page content and can host a left navigation bar.

import '@/styles/globals.css';
import SlideBar from '@/components/SlideBar';

function RootLayout({ children }) {
  return (
    <html lang='en'>
      <body>
        <main className='app'>
          <SlideBar />
          <div className='w-full h-full'>{children}</div>
        </main>
      </body>
    </html>
  );
}
export default RootLayout;

"use client" Directive

The "use client" directive marks a module as a client component, allowing the use of useState, useEffect, etc., which are prohibited in server components.

"use client";
import { useState, useEffect } from "react";
import Image from 'next/image';

const Home = () => {
  return (
    <div>
      <h1>Welcome to Next.js</h1>
      <p>This is the home page content.</p>
      {/* Use Next Image component */}
      <Image src="/path/to/your/image.jpg" alt="Description of the image" width={300} height={200} />
    </div>
  );
};
export default Home;

Component Import

Navigation links use the Link component for client‑side routing:

import React from 'react';
import Link from 'next/link';

const Home = () => {
  return (
    <div>
      <h1>Welcome to Next.js</h1>
      <p>This is the home page content.</p>
      <Link href="/category">跳转路由</Link>
    </div>
  );
};
export default Home;

Notion Database Integration

Notion can serve as a markdown‑based database. After creating a Notion database and an integration to obtain an API key, the project connects to it.

Create Notion Service

import { Client } from "@notionhq/client";
const auth = process.env.NOTION_AUTH;
const database = process.env.NOTION_DATABASE_ID;
export default class NotionService {
  constructor() {
    this.client = new Client({ auth });
  }
  async query() {
    const response = await this.client.databases.query({
      database_id: database,
    });
    return response.results;
  }
}

API Route

import NotionServer from "../../lib/NotionServer";
const notionServer = new NotionServer();
export default async function handler(req, res) {
  const data = await notionServer.query();
  res.status(200).json(data);
}

Visiting http://localhost:3000/api/question returns the raw Notion data, which can be filtered and displayed on the frontend.

Display on Frontend

"use client";
import { useState, useEffect } from "react";
import QuestionCard from '@/components/QuestionCard';

function Home() {
  const [questionList, setQuestionList] = useState([]);
  const [jsList, setJsList] = useState([]);

  const getQuestionList = () => {
    fetch('/api/question')
      .then(res => res.json())
      .then(res => {
        if (res) {
          setQuestionList(res.sort((a, b) => a.id - b.id));
        }
      })
      .catch(error => console.error(error));
  };

  useEffect(() => { getQuestionList(); }, []);
  useEffect(() => {
    const jsItems = questionList.filter(item => item.tags === "JavaScript");
    setJsList(jsItems);
  }, [questionList]);

  return (
    <div className="w-full h-full overflow-auto">
      <section className="gap-4 p-6 space-y-4 md:columns-2">
        <QuestionCard questionList={jsList} type="JavaScript" />
      </section>
    </div>
  );
}
export default Home;

Markdown Rendering

Content stored in Notion is markdown. The project uses markdown-it to convert markdown to HTML.

Setup

npm install markdown-it
"use client";
import { useState, useEffect } from 'react';
import markdownIt from 'markdown-it';
const md = new markdownIt();

function DialogCard({ data, closeDialog }) {
  const [htmlString, setHtmlString] = useState('');
  const parse = (data) => setHtmlString(md.render(data.explanation));
  useEffect(() => { parse(data); }, []);
  return (
    <div className='show w-full mt-1 flex-grow overflow-auto'>
      <div className='w-full' dangerouslySetInnerHTML={{ __html: htmlString }} />
    </div>
  );
}
export default DialogCard;

Tailwind’s Typography plugin ( @tailwindcss/typography) adds a prose class for nice default styling of rendered markdown.

npm install -D @tailwindcss/typography
module.exports = {
  plugins: [require("@tailwindcss/typography")],
};

Code blocks can be highlighted with highlight.js:

npm install highlight.js
import hljs from "highlight.js";
import "highlight.js/styles/monokai-sublime.css";
const md = new markdownIt({
  highlight: function (str, lang) {
    if (lang && hljs.getLanguage(lang)) {
      try {
        return hljs.highlight(str, { language: lang }).value;
      } catch (_) {}
    }
    return "";
  },
});

Deploy to Vercel

Log in to Vercel with a GitHub account, import the repository, and add the environment variables ( NOTION_AUTH and NOTION_DATABASE_ID) matching the local .env file. After deployment, the site is available at frontend-konwledge.vercel.app. Subsequent pushes automatically update the live site.

Conclusion

The article covered essential Next.js techniques used in the project, including SSR, dynamic routing, and metadata handling, which will be documented later with real‑world examples.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Next.jsTailwind CSSmarkdownVercelNotion API
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.