Next.js 13 博客文章数据获取与统计
import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
import { remark } from 'remark'
import html from 'remark-html'
import remarkPrism from "remark-prism";
import Info from '@/components/Info'
export const info = Info();
export const Directory = path.join(process.cwd(), info.path)
export function getAllPostIds() {
const subdirectories = fs.readdirSync(Directory, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name)
const posts = subdirectories.flatMap(subdirectory => {
const directoryPath = path.join(Directory, subdirectory)
const filenames = fs.readdirSync(directoryPath)
.filter(filename => filename.endsWith('.md'))
return filenames.map(filename => {
return {
params: {
slug: [subdirectory, filename.replace(/\.md$/, '.html')],
},
}
})
})
// Sort posts by date
// Format the path correctly
const paths = posts.map(({ params }) => ({ params: { slug: params.slug } }));
return paths;
}
export async function getSortedPostsData() {
const subdirectories = fs.readdirSync(Directory, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name)
const posts = (await Promise.all(subdirectories.map(async (subdirectory) => {
const directoryPath = path.join(Directory, subdirectory)
const filenames = fs.readdirSync(directoryPath)
.filter(filename => filename.endsWith('.md'))
const allPostsData = await Promise.all(filenames.map(async (filename) => {
const slug = [subdirectory, filename.replace(/\.md$/, '').concat('.html')].join('/')
const fullPath = path.join(Directory, [subdirectory, filename].join('//'))
const fileContents = fs.readFileSync(fullPath, 'utf8')
const matterResult = matter(fileContents)
const contentCount = await remark()
.use(html, { sanitize: false })
.process(matterResult.content)
const contCount = contentCount.toString().replace(/<\/?(h\d).*>/g, '');
const NoImages = contCount.replace(/<img[^>]*>/g, '').trim();
const NoHtml = NoImages.replace(/<\/?[^>]+(>|$)/g, '');
const cnMatches = NoHtml.match(/[一-龥]/g) || [];
const enMatches = NoHtml.match(/[a-zA-Z]+/g) || [];
const cnCount = cnMatches.length;
const enCount = enMatches.length;
const wordCount = cnCount + enCount - info.exclude;
const postData = {
slug,
wordCount,
...(matterResult.data as { date: string; title: string })
};
return postData;
}))
const sortedPostsData = allPostsData.reduce((acc, elems) => {
if(Array.isArray(elems)) {
return acc.concat(elems);
}
acc.push(elems);
return acc;
}, []);
return sortedPostsData.sort((a, b) => {
if (a.date < b.date) {
return 1
} else {
return -1
}
})
}))).flat()
return posts
}
export async function getPostData(slug:any) {
const idjoin = slug.toString().split(',').join('//').replace(/\.html$/, '')
const id = path.join(idjoin);
const fullPath = path.join(Directory, `${id}.md`)
const fileContents = fs.readFileSync(fullPath, 'utf8')
const matterResult = matter(fileContents)
const processedContent = await remark()
.use(html, { sanitize: false })
.use(remarkPrism, { plugins: ['line-numbers'] })
.process(matterResult.content)
const contentHtml = processedContent.toString()
const contentCount = await remark()
.use(html, { sanitize: false })
.process(matterResult.content)
const contCount = contentCount.toString().replace(/<\/?(h\d).*>/g, '');
const NoImages = contCount.replace(/<img[^>]*>/g, '').trim();
const NoHtml = NoImages.replace(/<\/?[^>]+(>|$)/g, '');
const cnMatches = NoHtml.match(/[一-龥]/g) || [];
const enMatches = NoHtml.match(/[a-zA-Z]+/g) || [];
const cnCount = cnMatches.length;
const enCount = enMatches.length;
const wordCount = cnCount + enCount - info.exclude;
console.log(`文章页面字数为:${wordCount}`);
const baseUrl = info.baseURL;
const imgRegex = /<img.*?src="(.*?)"/g;
let match: any[];
const imgUrlArr = [];
while ((match = imgRegex.exec(contentHtml)) !== null) {
const imgPath = match[1];
const imgUrl = `${baseUrl}${imgPath}`;
imgUrlArr.push(imgUrl);
}
const filePath = fullPath;
const fileStat = fs.statSync(filePath);
const mtime = fileStat.mtime.toISOString();
const birthtime = fileStat.birthtime.toISOString();
console.log(birthtime);
const summary = extractSummary(NoImages, 80);
const desc = summary.replace(/<\/?[^>]+(>|$)/g, '');
return {
birthtime,
mtime,
slug,
wordCount,
imgUrlArr,
summary,
desc,
contentHtml,
...(matterResult.data as { date: string; title: string; tags: string })
}
}
function extractSummary(content: string, minLength: number): string {
const filtered = content.replace(/<\/?(h\d).*>/g, '');
const paragraphs = filtered.split(/[
]+/);
let summary = '';
for (let i = 0; i < paragraphs.length; i++) {
const newSummaryLength = summary.length + paragraphs[i].length;
if (newSummaryLength < minLength) {
summary += paragraphs[i];
} else {
let j = i;
while (j < paragraphs.length && summary.length < minLength) {
summary += paragraphs[j];
j++;
}
break;
}
}
return summary.trim();
}
/lib/blog.ts
使用 Next.js 13 怎么把 /lib/blog.ts 里的数据输出到 /components/InfoTotal.tsx
import { SiteRuntime } from './SiteRuntime';
export default function InfoTotal() {
return (
<section className='info'>
<div className='info-fanma'>
<h2>导航信息</h2>
<ul>
<li><span className='l'></span><span className='r'></span></li>
<li><span className='l'>收录网站:</span><span className='r'></span></li>
<SiteRuntime />
</ul>
</div>
</section>
)
}
/components/InfoTotal.tsx内容:import { getAllPostIds, getSortedPostsData, Directory } from '../lib/blog'
export function SiteRuntime() {
const posts = getSortedPostsData();
const subdirectories = posts.map((post) => post.slug.split('/')[0]);
const uniqueSubdirectories = [...new Set(subdirectories)];
const postCount = posts.length;
const categoryCount = uniqueSubdirectories.length;
const stats = {
'文章总数': postCount,
'分类总数': categoryCount,
};
return (
<>
{Object.entries(stats).map(([key, value], index) => (
<li key={index}>
<span className='l'>{key}:</span>
<span className='r'>{value}</span>
</li>
))}
<li>
<span className='l'>最近更新:</span>
<span className='r'>{getLastUpdateTime(Directory)}</span>
</li>
</>
);
}
export function getLastUpdateTime(directory: string): string {
const subdirectories = fs.readdirSync(directory, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name)
const lastModified = Math.max(
...subdirectories.map((subdirectory) => {
const directoryPath = path.join(directory, subdirectory)
const filenames = fs.readdirSync(directoryPath)
.filter(filename => filename.endsWith('.md'))
.map(filename => path.join(Directory, [subdirectory, filename].join('//')))
const stats = filenames.map(filename => fs.statSync(filename))
const lastModified = Math.max(...stats.map(stat => stat.mtimeMs))
return lastModified
})
)
return new Date(lastModified).toLocaleDateString();
}
代码解读
代码实现了一个简单的博客文章数据获取和统计功能。
lib/blog.ts: 定义了三个函数,用于获取所有文章的 ID、获取所有文章的数据和获取单个文章的数据。getAllPostIds(): 获取所有文章的 ID,并以params.slug的形式存储。getSortedPostsData(): 获取所有文章的数据,包括文章标题、日期、字数等信息,并按照日期排序。getPostData(slug: any): 获取单个文章的数据,包括文章内容、图片路径、摘要、字数等信息。
components/InfoTotal.tsx: 使用lib/blog.ts中定义的函数获取文章数据,并统计文章总数、分类总数和最近更新时间。SiteRuntime(): 使用getSortedPostsData()获取所有文章数据,并统计文章总数、分类总数和最近更新时间。getLastUpdateTime(directory: string): 获取指定目录下的所有文件,并返回最近修改的时间。
总结
代码使用 Next.js 13 的特性,实现了博客文章数据的获取和统计功能,展示了如何在 Next.js 中处理静态数据和动态数据。
代码优化
- 为了提高代码的可读性和可维护性,可以将代码拆分成多个文件,例如将
getAllPostIds()和getSortedPostsData()函数分别放在lib/posts.ts和lib/post.ts文件中。 - 可以使用
data-fetching的方式,将数据预先加载到页面中,以提高页面加载速度。 - 可以使用
SWR或React Query等库来管理数据获取和缓存,以简化代码逻辑和提高代码效率。
其他
- 可以根据需要添加其他统计指标,例如文章阅读量、评论数等。
- 可以使用第三方库来生成文章摘要,以提供更好的用户体验。
- 可以使用 Next.js 的路由系统来创建博客页面,并实现文章列表、文章详情、分类页面等功能。
使用示例
// 在页面组件中使用 getSortedPostsData() 获取文章数据
import { getSortedPostsData } from '../lib/blog';
export default function Home() {
const posts = getSortedPostsData();
return (
<ul>
{posts.map((post) => (
<li key={post.slug}>
<a href={`/posts/${post.slug}`}>{post.title}</a>
</li>
))}
</ul>
);
}
// 在页面组件中使用 getPostData(slug) 获取单个文章数据
import { getPostData } from '../lib/blog';
export async function getStaticProps({ params }) {
const postData = await getPostData(params.slug);
return {
props: { postData },
};
}
export default function Post({ postData }) {
return (
<div>
<h1>{postData.title}</h1>
<p>{postData.contentHtml}</p>
</div>
);
}
原文地址: https://www.cveoy.top/t/topic/mYif 著作权归作者所有。请勿转载和采集!