“
作者:CraigBucklere原文:BuildaBlogwithReactandNext.js(sitepoint)字数:字(非直译,有添加部分)阅读:10分钟
大家好,在《动手练一练,使用React和Next.js做一个简单的博客网站(上)》一篇文章里,我们一起了解了什么是Next.js,并手工创建了一个简单的Next.js项目,学会了如何基于模板创建简单的页面,本篇文章,我们继续完善这个案例。
一、基于MD文档生成动态路由创建博客,自然少不了文章内容,如果我们每写一篇文章,就创建一个JSX单页面,这样太不现实,费事费力又不容易维护,我们开发人员更喜欢使用Markdown文档写文档。
庆幸的是,Next.js允许我们使用Markdown作为文章的数据源,基于文件名生成动态路由,并且实现文件内容的HTML静态化。
1、在编写本功能时,最好停止Next.js服务(Ctrl
Cmd+C)。
2、接下来,在项目的根目录里创建articles文件夹,把你的Markdown文件放置在这里,例如:articles/article-01.md,MD文档格式如下所示:
---title:Thefirstarticledescription:Thisisthefirstarticle.date:-10-01---Thisisanarticlepost.##SubheadingLoremipsumdolorsitamet,consecteturadipiscingelit.
我们将文档的标题名称、文档描述、创建日期放置在—之间,Front-matter这个npm插件基于这个格式可以读取上述相关信息提取文档的标题、描述、创建日期。要将MD文档格式化成网页的形式,我们还需要安装remark和remark-html这两个npm插件,安装命令如下:
npmifront-matterremarkremark-html
3、安装完成后,我们要实现读取和格式化MD文档的功能,接下来创建lib/posts-md.js工具函数文件。getFileIds(dir)函数返回一个MD文件名的数组(不包含.md扩展名的文件名),示例代码如下:
import{promisesasfsp}fromfs;importpathfrompath;importfmfromfront-matter;importremarkfromremark;importremarkhtmlfromremark-html;import*asdateformatfrom./dateformat;constfileExt=md;//returnabsolutepathtofolderfunctionabsPath(dir){return(path.isAbsolute(dir)?dir:path.resolve(process.cwd(),dir));}//returnarrayoffilesbytypeinadirectoryandremoveextensionsexportasyncfunctiongetFileIds(dir=./){constloc=absPath(dir);constfiles=awaitfsp.readdir(loc);returnfiles.filter((fn)=path.extname(fn)===`.${fileExt}`).map((fn)=path.basename(fn,path.extname(fn)));}
获取到文件名数组后,我们需要解析MD的具体内容,比如文件的标题、描述、创建日期、具体的内容HTML格式化等,示例代码如下:
exportasyncfunctiongetFileData(dir=./,id){constfile=path.join(absPath(dir),`${id}.${fileExt}`),stat=awaitfsp.stat(file),data=awaitfsp.readFile(file,utf8),matter=fm(data),html=(awaitremark().use(remarkhtml).process(matter.body)).toString();//dateformattingconstdate=matter.attributes.date
stat.ctime;matter.attributes.date=date.toUTCString();matter.attributes.dateYMD=dateformat.ymd(date);matter.attributes.dateFriendly=dateformat.friendly(date);//wordcountconstroundTo=10,readPerMin=,numFormat=newIntl.NumberFormat(en),count=matter.body.replace(/\W/g,).replace(/\s+/g,).split().length,words=Math.ceil(count/roundTo)*roundTo,mins=Math.ceil(count/readPerMin);matter.attributes.wordcount=`${numFormat.format(words)}words,${numFormat.format(mins)}-minuteread`;return{id,html,...matter.attributes};}
你可能注意到我使用了日期格式化功能,其功能定义在lib/dateformat.js文件,示例代码如下:
//dateformattingfunctionsconsttoMonth=newIntl.DateTimeFormat(en,{month:long});//formatadatetoYYYY-MM-DDexportfunctionymd(date){returndateinstanceofDate?`${date.getUTCFullYear()}-${String(date.getUTCMonth()+1).padStart(2,0)}-${String(date.getUTCDate()).padStart(2,0)}`:;}//formatadatetoDDMMMM,YYYYexportfunctionfriendly(date){returndateinstanceofDate?`${date.getUTCDate()}${toMonth.format(date)},${date.getUTCFullYear()}`:;}
4、Next.js使用带[]符号的特殊的文件名生成动态路由。接下来我们在Pages目录下创建这个特殊的文件pages/articles/[id].js,Next.js使用id作为路由的参数,生成/articles/article-01的页面路由。
pages/articles/[id].js这个文件里实现Next.js特有的getStaticPaths()函数功能(StaticGeneration),在项目构建时生成指定的路由路径,比如这个案例将articles目录下的MD文档返回如下的数组格式,id将匹配pages/articles/[id].js对应的[id]参数生成动态路由:
[{params:{id:"article-01"}},{params:{id:"article-02"}},{params:{id:"article-03"}},...]
这个方法调用lib/posts-md.js文件里读取getFileIds文件路径列表的方法,示例代码如下:
import{getFileIds,getFileData}from../../lib/posts-md;//postdirectoryconstpostsDir=articles;//dynamicrouteIDsexportasyncfunctiongetStaticPaths(){constpaths=(awaitgetFileIds(postsDir)).map((id)=({params:{id}}));return{paths,fallback:false,};}
5、动态路由生成后,我们需要实现MD内容格式化渲染,我们实现Next.js特有的异步方法getStaticProps({params}),在项目构建时调用这个函数(StaticGeneration),通过id参数调用lib/posts-md.js文件中getFileData()定义的方法,将MD文档内容异步回传至包含postData属性的组件内部(第六点的代码部分),示例代码如下:
//dynamicroutecontentexportasyncfunctiongetStaticProps({params}){return{props:{postData:awaitgetFileData(postsDir,params.id),},};}
6、拿到数据后,我们需要填充到组件的模板里,以更友好的形式展现,我们在pages/articles/[id].js里编写JSX的相关代码,将文章内容嵌套在上节组件模板内,示例代码如下:
importLayoutfrom../../