{"id":14989536,"url":"https://github.com/weibozzz/next-blog","last_synced_at":"2025-07-21T17:31:39.833Z","repository":{"id":108037453,"uuid":"139151493","full_name":"Weibozzz/next-blog","owner":"Weibozzz","description":"基于react(ssr)服务端框架next.js和antd-design搭建的个人博客","archived":false,"fork":false,"pushed_at":"2021-09-18T03:10:28.000Z","size":14557,"stargazers_count":237,"open_issues_count":6,"forks_count":36,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-08T04:18:36.273Z","etag":null,"topics":["antd","fetch","koa2","mysql","nextjs","nodejs","react","seo","ssr"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Weibozzz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-06-29T13:15:15.000Z","updated_at":"2025-04-05T08:02:36.000Z","dependencies_parsed_at":null,"dependency_job_id":"22873bb7-ace0-46f3-8006-c7845b2f009e","html_url":"https://github.com/Weibozzz/next-blog","commit_stats":{"total_commits":186,"total_committers":4,"mean_commits":46.5,"dds":0.446236559139785,"last_synced_commit":"7019b5847c50d46858377ff92b7e8355d70aef04"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Weibozzz/next-blog","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Weibozzz%2Fnext-blog","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Weibozzz%2Fnext-blog/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Weibozzz%2Fnext-blog/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Weibozzz%2Fnext-blog/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Weibozzz","download_url":"https://codeload.github.com/Weibozzz/next-blog/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Weibozzz%2Fnext-blog/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266342809,"owners_count":23914262,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-21T11:47:31.412Z","response_time":64,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["antd","fetch","koa2","mysql","nextjs","nodejs","react","seo","ssr"],"created_at":"2024-09-24T14:18:31.614Z","updated_at":"2025-07-21T17:31:39.788Z","avatar_url":"https://github.com/Weibozzz.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# next-blog\n\n## 项目介绍\n利用react服务端框架next.js写的博客，喜欢就给个Star支持一下。\n[https://github.com/Weibozzz/next-blog](https://github.com/Weibozzz/next-blog)\n线上地址： [http://www.liuweibo.cn](http://www.liuweibo.cn)\n本项目使用next.js经验分享：[http://www.liuweibo.cn/p/206](http://www.liuweibo.cn/p/206)\n另外还有一个仓库，建立自己的前端知识体系：[https://github.com/Weibozzz/Weibozzz.github.io](https://github.com/Weibozzz/Weibozzz.github.io)\n\n\n## 软件架构\n软件架构说明\n`react.js next.js antd mysql node koa2 fetch `\n\n## 网站使用技术\n\n- 前端：React(16.x) Next.js antd-design fetch Less\n- 后端：node框架koa和mysql （目前前后端分离，这里只负责写接口，和平常的ajax获取接口一样，这里就不开放源码了）\n- 网站目的：业余学习，记录技术文章，学以致用\n- 网站功能\n    - markdown发布文章\n    - 修改文章（增删改查）\n    - 用户评论\n    - 上传图片到七牛云存储\n    - 点击图片预览功能\n    - 支持手机自适应\n\n## 安装教程\n\n1. 快速开始\n虽然是服务端渲染，但是也要调用接口，所以需要调用后端的接口\n\n**进入config文件夹下的env.js的isShow设置为true**,这里只是调用了我自己线上的接口，当然你\n只能看不能修改接口哦。如果为false则调不到接口，需要自己去写接口。\n\n\n2. 运行\n```bash\ncnpm i\nnpm run dev\n```\n3. 部署\n```bash\ncnpm i\nnpm run build\nnpm start\n```\n\n\n## 使用说明\n\n- 关于演示不能上传图片，不能发表文章或者修改属于正常情况，因为只是为了展示。\n- 关于路看不到发布文章路由和后台管理也属于正常情况，可以修改代码展示路由效果。\n\n## 网站截图\n\n1. 详情页\n![http://images.liuweibo.cn/image/common/detail_1536836727000_459470_1536836749510.png](http://images.liuweibo.cn/image/common/detail_1536836727000_459470_1536836749510.png)\n2. 列表页\n![http://images.liuweibo.cn/image/common/list_1536836639000_822188_1536836780676.png](http://images.liuweibo.cn/image/common/list_1536836639000_822188_1536836780676.png)\n3. 编辑页面和发布文章，上传图片到七牛云\n![http://images.liuweibo.cn/image/common/edit_1536836607000_802376_1536836825962.png](http://images.liuweibo.cn/image/common/edit_1536836607000_802376_1536836825962.png)\n\n\n## 网站技术介绍\n\u003e 完全借助于 [next.js](https://github.com/zeit/next.js) 开发的个人网站，线上地址 [http://www.liuweibo.cn](http://www.liuweibo.cn) 总结一下开发完成后的心得和使用体会。gtihub源码[https://github.com/Weibozzz/next-blog](https://github.com/Weibozzz/next-blog)。喜欢就给个Star支持一下。\n\n### 为什么使用服务器端渲染(SSR)？\n- 网站是要推广的，所以需要更好的 SEO，搜索引擎可以抓取完整页面\n- 访问速度，更快的加载静态页面\n\n### 网站使用技术\n\n- 前端：React(16.x) Next.js antd-design fetch Less\n- 后端：node框架koa和mysql （目前前后端分离，这里只负责写接口，和平常的ajax获取接口一样，这里就不开放源码了）\n- 网站目的：业余学习，记录技术文章，学以致用\n- 网站功能\n    - 发布文章\n    - 修改文章（增删改查）\n    - 用户评论\n\n### 源码剖析\n\u003e 这里就只讲重点了\n\n#### 入口文件`server.js`\n这里用的官方提供的`express`，同时开启`gzip`压缩\n\n```js\nconst express = require('express')\nconst next = require('next')\n\nconst compression = require('compression')\nconst dev = process.env.NODE_ENV !== 'production'\nconst app = next({ dev })\nconst handle = app.getRequestHandler()\nlet port= dev?4322:80\n\napp.prepare()\n  .then(() =\u003e {\n    const server = express()\n\n    if (!dev) {\n      server.use(compression()) //gzip\n    }\n    //文章二级页面\n    server.get('/p/:id', (req, res) =\u003e {\n      const actualPage = '/detail'\n      const queryParams = { id: req.params.id }\n      app.render(req, res, actualPage, queryParams)\n    })\n\n    server.get('*', (req, res) =\u003e {\n      return handle(req, res)\n    })\n\n    server.listen(port, (err) =\u003e {\n      if (err) throw err\n      console.log('\u003e Ready on http://localhost ' port)\n    })\n  })\n  .catch((ex) =\u003e {\n    process.exit(1)\n  })\n\n```\n#### page根组件_app.js\n用于传递redux数据,store就和普通react用法一样了，还有header和footer可以放在这里,同理还有`_err.js`用于处理404页面\n\n```js\n\nimport App, {Container} from 'next/app'\nimport React from 'react'\nimport {withRouter} from 'next/router' // 接入next的router\nimport withReduxStore from '../lib/with-redux-store' // 接入next的redux\nimport {Provider} from 'react-redux'\n\n\nclass MyApp extends App {\n  render() {\n\n    const {Component, pageProps, reduxStore, router: {pathname}} = this.props;\n    return (\n      \u003cContainer\u003e\n        \u003cProvider store={reduxStore}\u003e\n         \u003cComponent {...myPageProps}  /\u003e\n        \u003c/Provider\u003e\n\n      \u003c/Container\u003e\n    )\n  }\n}\n\nexport default withReduxStore(withRouter(MyApp))\n\n```\n#### 网站的服务端渲染页面Blog页面\n - `link`用于跳转页面，利用as把原本的http://***.com?id=1变为漂亮的 /id/1\n - `head`可以嵌套meta标签进行seo\n - 配置不需要seo的组件\n\n```js\nimport dynamic from 'next/dynamic';\n\n//不需要seo\nconst DynasicTopTipsNoSsr = dynamic(import('../../components/TopTips'),{\n  ssr:false\n})\n\nimport React, {Component} from 'react'\nimport {connect} from 'react-redux'\nimport Router from 'next/router'\nimport 'whatwg-fetch' // 用于fetch请求数据\nimport Link from 'next/link'; // next的跳转link\nimport Head from 'next/head'  // next的跳转head可用于seo\n\nclass Blog extends Component {\n\n  render() {\n    return (\n      \u003cdiv className=\"Blog\"\u003e\n        \u003cHead\u003e\n          \u003ctitle\u003e{BLOG_TXT}\u0026raquo;{COMMON_TITLE}\u003c/title\u003e\n        \u003c/Head\u003e\n        \u003cMyLayout\u003e\n          \u003cLink   as={`/Blog/${current}`} href={`/Blog?id=${current}`}\u003e\n            \u003ca onClick={this.onClickPageChange.bind(this)}\u003e{current}\u003c/a\u003e\n          \u003c/Link\u003e\n        \u003c/MyLayout\u003e\n      \u003c/div\u003e\n    )\n  }\n}\n//这里才是重点，getInitialProps方法来请求数据进行渲染，达到服务端渲染的目的\nBlog.getInitialProps = async function (context) {\n  const {id = 1} = context.query\n  let queryStringObj = {\n    type: ALL,\n    num: id,\n    pageNum\n  }\n  let queryTotalString = {type: ALL};\n  const pageBlog = await fetch(getBlogUrl(queryStringObj))\n  const pageBlogData = await pageBlog.json()\n\n\n  return {pageBlogData}\n}\n// 这里根据需要传入redux\nconst mapStateToProps = state =\u003e {\n  const {res, searchData, searchTotalData} = state\n  return {res, searchData, searchTotalData};\n}\nexport default connect(mapStateToProps)(Blog)\n\n```\n\n#### 静态资源\n根目录创建`static`文件夹，这里是强制要求，否则加载不到静态资源\n#### 配置antd和主题并且按需加载\n#### 主题配置\nantd-custom.less\n```css\n@primary-color: #722ED0;\n\n@layout-header-height: 40px;\n@border-radius-base: 0px;\n\n```\nstyles.less\n```css\n@import \"~antd/dist/antd.less\";\n@import \"./antd-custom.less\";\n\n```\n最后统一配置在公共`head`\n```html\n\u003cHead\u003e\n    \u003cmeta charSet=\"utf-8\"/\u003e\n    \u003cmeta httpEquiv=\"X-UA-Compatible\" content=\"IE=edge, chrome=1\"/\u003e\n    \u003cmeta name=\"viewport\"\n          content=\"width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no\"/\u003e\n    \u003cmeta name=\"renderer\" content=\"webkit\"/\u003e\n    \u003cmeta httpEquiv=\"description\" content=\"刘伟波-伟波前端\"/\u003e\n    \u003cmeta name=\"author\" content=\"刘伟波,liuweibo\"/\u003e\n    \u003clink rel='stylesheet' href='/_next/static/style.css'/\u003e\n    \u003clink rel='stylesheet' type='text/css' href='/static/nprogress.css' /\u003e\n    \u003clink rel='shortcut icon' type='image/x-icon' href='/static/favicon.ico' /\u003e\n  \u003c/Head\u003e\n\n```\n#### 按需加载配置\n`.babelrc`文件\n\n```json\n{\n  \"presets\": [\"next/babel\"],\n  \"plugins\": [\n    \"transform-decorators-legacy\",\n    [\n      \"import\",\n      {\n        \"libraryName\": \"antd\",\n        \"style\": \"less\"\n      }\n    ]\n  ]\n}\n\n```\n`next.config.js`文件配置\n```js\nconst withLess = require('@zeit/next-less')\n\nmodule.exports =   withLess(\n  {\n    lessLoaderOptions: {\n      javascriptEnabled: true,\n      cssModules: true,\n\n    }\n  }\n)\n\n```\n#### 页面css\n感觉和`vue`的`scope`一样，`style`的`jsx`,加了`global`为全局，否则只在这里生效\n```js\nrender() {\n\n    return (\n      \u003cContainer\u003e\n        \u003cProvider store={reduxStore}\u003e\n          \u003cComponent {...myPageProps}  /\u003e\n        \u003c/Provider\u003e\n\n        \u003cstyle jsx global\u003e{`\n\n.fl{\n    float: left;\n}\n.fr{\n    float: right;\n}\n        `}\u003c/style\u003e\n      \u003c/Container\u003e\n    )\n```\n\n#### 页面顶部加载进度条\n```js\nimport Router from 'next/router'\nimport NProgress from 'nprogress'\n\nRouter.onRouteChangeStart = (url) =\u003e {\n  NProgress.start()\n}\nRouter.onRouteChangeComplete = () =\u003e NProgress.done()\nRouter.onRouteChangeError = () =\u003e NProgress.done()\n```\n\n#### markdown发表文章和代码高亮\n使用只需要`marked('放入markdown字符串');`\n```js\nimport marked from 'marked'\nimport hljs from 'highlight.js';\n\nhljs.configure({\n  tabReplace: '  ',\n  classPrefix: 'hljs-',\n  languages: ['CSS', 'HTML, XML', 'JavaScript', 'PHP', 'Python', 'Stylus', 'TypeScript', 'Markdown']\n})\nmarked.setOptions({\n  highlight: (code) =\u003e hljs.highlightAuto(code).value,\n  gfm: true,\n  tables: true,\n  breaks: false,\n  pedantic: false,\n  sanitize: true,\n  smartLists: true,\n  smartypants: false\n});\n```\n\n#### next.js 配置相关\n`next.config.js`文件配置\n```js\nmodule.exports = {\n  webpack (config, ...args) {\n    return config;\n  }\n}\n```\n- 增加 alias\n```\nconfig.resolve.alias = {\n      ...config.resolve.alias,\n      '@': path.resolve(__dirname, './'),\n    }\n\n```\n- 增加插件\n\n```\nconfig.plugins.push(\n      new webpack.DefinePlugin({\n        'process.env.NODE_ENV': process.env.NODE_ENV\n      })\n    )\n```\n\n- 增加 rules\n```\nconfig.module.rules.push(\n      {\n        test: /\\.svg(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        use: [\n          {\n            loader: 'babel-loader'\n          },\n          {\n            loader: '@svgr/webpack',\n            options: {\n              babel: false,\n              icon: true\n            }\n          }\n        ]\n      }\n    )\n```\n\n- 支持css和less\n\n```\nconfig = withCSS()\n      .webpack(config, ...args)\n    config = withLess()\n      .webpack(config, ...args)\n```\n- less某些文件开启css module\n```\nconst loaderUtils = require('loader-utils')\nconst fs = require('fs')\nconst path = require('path')\nconst cssModuleRegex = /\\.module\\.less$/\n\nconfig = withLess(\n      // 开启 css module 自定义\n      {\n        cssModules: true,\n        cssLoaderOptions: {\n          importLoaders: 1,\n          minimize: !args[0].dev,\n          getLocalIdent: (loaderContext, _, localName, options) =\u003e {\n            const fileName = path.basename(loaderContext.resourcePath)\n            if (cssModuleRegex.test(fileName)) {\n              const content = fs.readFileSync(loaderContext.resourcePath)\n                .toString()\n              const name = fileName.replace(/\\.[^/.]+$/, '')\n              const hash = args[0].dev ? `${name}___[hash:base64:5]` : '[hash:base64:5]'\n              const fileNameHash = loaderUtils.interpolateName(\n                loaderContext,\n                hash,\n                { content }\n              )\n              return fileNameHash\n            }\n            return localName\n          }\n        }\n      }\n    )\n```\n- 支持 bundleAnalyzer\n```\nconst withBundleAnalyzer = require('@next/bundle-analyzer')({\n  enabled: process.env.ANALYZE === 'true'\n})\n\n    config = withBundleAnalyzer({})\n      .webpack(config, ...args)\n```\n## 学累了，来个图放松下\n\n![http://images.liuweibo.cn/image/common/2a35e89324d3ad64d52683ad1343732e_1535531349000_84470_1535531469641.jpg](http://images.liuweibo.cn/image/common/2a35e89324d3ad64d52683ad1343732e_1535531349000_84470_1535531469641.jpg))\n\n\n\n\n\n\n\n## 参与贡献\n\n1. Fork 本项目\n2. 新建 Feat_xxx 分支\n3. 提交代码\n4. 新建 Pull Request\n\n\n## 遗留问题\n\n1. 访问量大的时候要做数据缓存\n2. cdn  node查看图片日期\n3. 配置图片描述和更改\n4. 上传图片高质量暂未支持上传,上传代码改进\n7. 上传为刚好1M bug\n10. 登陆后支持收藏文章和修改评论\n7. 发表文章如果上一篇文章被删除，自增id不是自加1，需要去查询数据库\n\n9. 填写markdown代码高亮，类似于掘金\n10. versions当天的要合并\n\n\n### 待学习修改\n1. 开发环境 warning.js:33 Warning: A component is `contentEditable`\n5. eslint\n\n## 关于作者 / About\n\n- github:[https://github.com/Weibozzz](https://github.com/Weibozzz)\n- 个人博客:[http://www.liuweibo.cn](http://www.liuweibo.cn)\n- segmentfault:[https://segmentfault.com/u/weibozzz](https://segmentfault.com/u/weibozzz)\n- 公众平台：伟波前端\n\n    \u003cimg height=\"150\" src=\"http://weibozzz.gitee.io/some-imgs/wx/qrcode_white.png\" /\u003e\n\n\n## 版权声明\n- 所有原创文章的著作权属于 Weibozzz。\n\n作者：刘伟波\n\n链接：[http://www.liuweibo.cn/p/206](http://www.liuweibo.cn/p/206)\n\n来源：[刘伟波博客](http://www.liuweibo.cn)\n\n本文原创版权属于刘伟波 ，转载请注明出处，谢谢合作\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fweibozzz%2Fnext-blog","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fweibozzz%2Fnext-blog","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fweibozzz%2Fnext-blog/lists"}