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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
