Giter Club home page Giter Club logo

next-blog's Introduction

next-blog

项目介绍

利用react服务端框架next.js写的博客,喜欢就给个Star支持一下。 https://github.com/Weibozzz/next-blog 线上地址: http://www.liuweibo.cn 本项目使用next.js经验分享:http://www.liuweibo.cn/p/206 另外还有一个仓库,建立自己的前端知识体系:https://github.com/Weibozzz/Weibozzz.github.io

软件架构

软件架构说明 react.js next.js antd mysql node koa2 fetch

网站使用技术

  • 前端:React(16.x) Next.js antd-design fetch Less
  • 后端:node框架koa和mysql (目前前后端分离,这里只负责写接口,和平常的ajax获取接口一样,这里就不开放源码了)
  • 网站目的:业余学习,记录技术文章,学以致用
  • 网站功能
    • markdown发布文章
    • 修改文章(增删改查)
    • 用户评论
    • 上传图片到七牛云存储
    • 点击图片预览功能
    • 支持手机自适应

安装教程

  1. 快速开始 虽然是服务端渲染,但是也要调用接口,所以需要调用后端的接口

进入config文件夹下的env.js的isShow设置为true,这里只是调用了我自己线上的接口,当然你 只能看不能修改接口哦。如果为false则调不到接口,需要自己去写接口。

  1. 运行
cnpm i
npm run dev
  1. 部署
cnpm i
npm run build
npm start

使用说明

  • 关于演示不能上传图片,不能发表文章或者修改属于正常情况,因为只是为了展示。
  • 关于路看不到发布文章路由和后台管理也属于正常情况,可以修改代码展示路由效果。

网站截图

  1. 详情页 http://images.liuweibo.cn/image/common/detail_1536836727000_459470_1536836749510.png
  2. 列表页 http://images.liuweibo.cn/image/common/list_1536836639000_822188_1536836780676.png
  3. 编辑页面和发布文章,上传图片到七牛云 http://images.liuweibo.cn/image/common/edit_1536836607000_802376_1536836825962.png

网站技术介绍

完全借助于 next.js 开发的个人网站,线上地址 http://www.liuweibo.cn 总结一下开发完成后的心得和使用体会。gtihub源码https://github.com/Weibozzz/next-blog。喜欢就给个Star支持一下。

为什么使用服务器端渲染(SSR)?

  • 网站是要推广的,所以需要更好的 SEO,搜索引擎可以抓取完整页面
  • 访问速度,更快的加载静态页面

网站使用技术

  • 前端:React(16.x) Next.js antd-design fetch Less
  • 后端:node框架koa和mysql (目前前后端分离,这里只负责写接口,和平常的ajax获取接口一样,这里就不开放源码了)
  • 网站目的:业余学习,记录技术文章,学以致用
  • 网站功能
    • 发布文章
    • 修改文章(增删改查)
    • 用户评论

源码剖析

这里就只讲重点了

入口文件server.js

这里用的官方提供的express,同时开启gzip压缩

const express = require('express')
const next = require('next')

const compression = require('compression')
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
let port= dev?4322:80

app.prepare()
  .then(() => {
    const server = express()

    if (!dev) {
      server.use(compression()) //gzip
    }
    //文章二级页面
    server.get('/p/:id', (req, res) => {
      const actualPage = '/detail'
      const queryParams = { id: req.params.id }
      app.render(req, res, actualPage, queryParams)
    })

    server.get('*', (req, res) => {
      return handle(req, res)
    })

    server.listen(port, (err) => {
      if (err) throw err
      console.log('> Ready on http://localhost ' port)
    })
  })
  .catch((ex) => {
    process.exit(1)
  })

page根组件_app.js

用于传递redux数据,store就和普通react用法一样了,还有header和footer可以放在这里,同理还有_err.js用于处理404页面

import App, {Container} from 'next/app'
import React from 'react'
import {withRouter} from 'next/router' // 接入next的router
import withReduxStore from '../lib/with-redux-store' // 接入next的redux
import {Provider} from 'react-redux'


class MyApp extends App {
  render() {

    const {Component, pageProps, reduxStore, router: {pathname}} = this.props;
    return (
      <Container>
        <Provider store={reduxStore}>
         <Component {...myPageProps}  />
        </Provider>

      </Container>
    )
  }
}

export default withReduxStore(withRouter(MyApp))

网站的服务端渲染页面Blog页面

  • link用于跳转页面,利用as把原本的http://***.com?id=1变为漂亮的 /id/1
  • head可以嵌套meta标签进行seo
  • 配置不需要seo的组件
import dynamic from 'next/dynamic';

//不需要seo
const DynasicTopTipsNoSsr = dynamic(import('../../components/TopTips'),{
  ssr:false
})

import React, {Component} from 'react'
import {connect} from 'react-redux'
import Router from 'next/router'
import 'whatwg-fetch' // 用于fetch请求数据
import Link from 'next/link'; // next的跳转link
import Head from 'next/head'  // next的跳转head可用于seo

class Blog extends Component {

  render() {
    return (
      <div className="Blog">
        <Head>
          <title>{BLOG_TXT}&raquo;{COMMON_TITLE}</title>
        </Head>
        <MyLayout>
          <Link   as={`/Blog/${current}`} href={`/Blog?id=${current}`}>
            <a onClick={this.onClickPageChange.bind(this)}>{current}</a>
          </Link>
        </MyLayout>
      </div>
    )
  }
}
//这里才是重点,getInitialProps方法来请求数据进行渲染,达到服务端渲染的目的
Blog.getInitialProps = async function (context) {
  const {id = 1} = context.query
  let queryStringObj = {
    type: ALL,
    num: id,
    pageNum
  }
  let queryTotalString = {type: ALL};
  const pageBlog = await fetch(getBlogUrl(queryStringObj))
  const pageBlogData = await pageBlog.json()


  return {pageBlogData}
}
// 这里根据需要传入redux
const mapStateToProps = state => {
  const {res, searchData, searchTotalData} = state
  return {res, searchData, searchTotalData};
}
export default connect(mapStateToProps)(Blog)

静态资源

根目录创建static文件夹,这里是强制要求,否则加载不到静态资源

配置antd和主题并且按需加载

主题配置

antd-custom.less

@primary-color: #722ED0;

@layout-header-height: 40px;
@border-radius-base: 0px;

styles.less

@import "~antd/dist/antd.less";
@import "./antd-custom.less";

最后统一配置在公共head

<Head>
    <meta charSet="utf-8"/>
    <meta httpEquiv="X-UA-Compatible" content="IE=edge, chrome=1"/>
    <meta name="viewport"
          content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no"/>
    <meta name="renderer" content="webkit"/>
    <meta httpEquiv="description" content="刘伟波-伟波前端"/>
    <meta name="author" content="刘伟波,liuweibo"/>
    <link rel='stylesheet' href='/_next/static/style.css'/>
    <link rel='stylesheet' type='text/css' href='/static/nprogress.css' />
    <link rel='shortcut icon' type='image/x-icon' href='/static/favicon.ico' />
  </Head>

按需加载配置

.babelrc文件

{
  "presets": ["next/babel"],
  "plugins": [
    "transform-decorators-legacy",
    [
      "import",
      {
        "libraryName": "antd",
        "style": "less"
      }
    ]
  ]
}

next.config.js文件配置

const withLess = require('@zeit/next-less')

module.exports =   withLess(
  {
    lessLoaderOptions: {
      javascriptEnabled: true,
      cssModules: true,

    }
  }
)

页面css

感觉和vuescope一样,stylejsx,加了global为全局,否则只在这里生效

render() {

    return (
      <Container>
        <Provider store={reduxStore}>
          <Component {...myPageProps}  />
        </Provider>

        <style jsx global>{`

.fl{
    float: left;
}
.fr{
    float: right;
}
        `}</style>
      </Container>
    )

页面顶部加载进度条

import Router from 'next/router'
import NProgress from 'nprogress'

Router.onRouteChangeStart = (url) => {
  NProgress.start()
}
Router.onRouteChangeComplete = () => NProgress.done()
Router.onRouteChangeError = () => NProgress.done()

markdown发表文章和代码高亮

使用只需要marked('放入markdown字符串');

import marked from 'marked'
import hljs from 'highlight.js';

hljs.configure({
  tabReplace: '  ',
  classPrefix: 'hljs-',
  languages: ['CSS', 'HTML, XML', 'JavaScript', 'PHP', 'Python', 'Stylus', 'TypeScript', 'Markdown']
})
marked.setOptions({
  highlight: (code) => hljs.highlightAuto(code).value,
  gfm: true,
  tables: true,
  breaks: false,
  pedantic: false,
  sanitize: true,
  smartLists: true,
  smartypants: false
});

next.js 配置相关

next.config.js文件配置

module.exports = {
  webpack (config, ...args) {
    return config;
  }
}
  • 增加 alias
config.resolve.alias = {
      ...config.resolve.alias,
      '@': path.resolve(__dirname, './'),
    }

  • 增加插件
config.plugins.push(
      new webpack.DefinePlugin({
        'process.env.NODE_ENV': process.env.NODE_ENV
      })
    )
  • 增加 rules
config.module.rules.push(
      {
        test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
        use: [
          {
            loader: 'babel-loader'
          },
          {
            loader: '@svgr/webpack',
            options: {
              babel: false,
              icon: true
            }
          }
        ]
      }
    )
  • 支持css和less
config = withCSS()
      .webpack(config, ...args)
    config = withLess()
      .webpack(config, ...args)
  • less某些文件开启css module
const loaderUtils = require('loader-utils')
const fs = require('fs')
const path = require('path')
const cssModuleRegex = /\.module\.less$/

config = withLess(
      // 开启 css module 自定义
      {
        cssModules: true,
        cssLoaderOptions: {
          importLoaders: 1,
          minimize: !args[0].dev,
          getLocalIdent: (loaderContext, _, localName, options) => {
            const fileName = path.basename(loaderContext.resourcePath)
            if (cssModuleRegex.test(fileName)) {
              const content = fs.readFileSync(loaderContext.resourcePath)
                .toString()
              const name = fileName.replace(/\.[^/.]+$/, '')
              const hash = args[0].dev ? `${name}___[hash:base64:5]` : '[hash:base64:5]'
              const fileNameHash = loaderUtils.interpolateName(
                loaderContext,
                hash,
                { content }
              )
              return fileNameHash
            }
            return localName
          }
        }
      }
    )
  • 支持 bundleAnalyzer
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true'
})

    config = withBundleAnalyzer({})
      .webpack(config, ...args)

学累了,来个图放松下

http://images.liuweibo.cn/image/common/2a35e89324d3ad64d52683ad1343732e_1535531349000_84470_1535531469641.jpg)

参与贡献

  1. Fork 本项目
  2. 新建 Feat_xxx 分支
  3. 提交代码
  4. 新建 Pull Request

遗留问题

  1. 访问量大的时候要做数据缓存

  2. cdn node查看图片日期

  3. 配置图片描述和更改

  4. 上传图片高质量暂未支持上传,上传代码改进

  5. 上传为刚好1M bug

  6. 登陆后支持收藏文章和修改评论

  7. 发表文章如果上一篇文章被删除,自增id不是自加1,需要去查询数据库

  8. 填写markdown代码高亮,类似于掘金

  9. versions当天的要合并

待学习修改

  1. 开发环境 warning.js:33 Warning: A component is contentEditable
  2. eslint

关于作者 / About

版权声明

  • 所有原创文章的著作权属于 Weibozzz。

作者:刘伟波

链接:http://www.liuweibo.cn/p/206

来源:刘伟波博客

本文原创版权属于刘伟波 ,转载请注明出处,谢谢合作

next-blog's People

Contributors

weibozzz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

next-blog's Issues

翻页获取 this.state.currentPage 总是1

componentWillMount() {
console.log("componentWillMount")
console.log("componentWillMount"+this.state.currentPage)

const {dispatch} = this.props;
const queryTotalString = {
  type: 'hot',

};
getHotArticleList(dispatch, getBlogUrl(queryTotalString))
getViewList(dispatch, getViewUrl())
getCreateTimeList(dispatch, getCreateTimeUrl())

}

async componentDidMount() {
console.log("componentDidMount")
const {router = {}} = Router
const {query = {}} = router
const {id = '1'} = query
this.setState({
currentPage: Number(id)
})

const {dispatch} = this.props;
const realIp = await real_ip()
let queryParamsObj = {real_ip: realIp, ip: returnCitySN['cip'], address: returnCitySN['cname']};
//存取用户ip
postSaveIp(dispatch, postSaveIpUrl(), queryParamsObj)

}

如何解決從後端取值之後的ssr問題?

你好,我正在學習用next.js解決ssr的問題。
請問一個前後端分離了項目,你是如何解決從後端取值之後的ssr問題?
我將程式碼精簡如下,希望能幫助說明我的問題:

import React, { Component } from 'react';
class Test extends Component {
    constructor(){
        super();
        this.state={
            'test':''
        }
    }

    setTest(){

        axios.get(serverName+'/api/articles/GET/test').then(response=>{

            let test;
            test = response.data.test;
            this.setState({test});

        }).catch(function (error) {
            console.log(error);
        });
    }
    }

    render() {
        return (
            <div>
                {this.state.test}
            </div>
        );
    }
}
export default Test;

後端使用laravel,僅僅用於回傳一個字串

function getTest(Request $request){
    return response()->json(['test'=>'this is a test']);
}

上述的程式碼,從browser檢查原始碼時,會發現“this is a test”字串並未被server side 取得。
請問這個問題你是如何解決的?

getInitialProps请求本地接口报错

场景:在index页面下的getInitialProps 下,请求本地接口(koa2作为后端)服务,控制台报错
错误提示:Request failed with status code 404
代码:

 static async getInitialProps({ req }) {
    await axios.post(`/api/get/banner`).then(response => {
      console.log(response.data.data, "--------------");
    });
    return { banners: response.data.data };
  }
//如果把这样的绝对路径‘/api/get/banner’,换成https://api.data.caasdata.com/rank?data_counter=1,线上地址就不会报错

个人觉得,应该不是后台代码(koa2)接口的问题,因为同时有登录、注册页面的请求,是可以成功的,然而获取首页数据想放在getInitialProps下,来获取本地api就报错,请大佬指点一下。

antd nextjs cssModules:true ?

原本就是因为项目中需要引用less,首先我可能要改变antd的主题,我可能需要统一的引用 antd 的变量 覆盖在的我的项目中
styled-jsx 并不方便 而且依赖的less 版本 和 less-loader 并不对等,
而且只要开启了css module :true,atnd 的class 会被转换

https://github.com/Weibozzz/next-blog/blob/master/next.config.js#L8
你现在项目之所以没问题 因为这个参数cssModules应该和lessLoaderOptions 同级,�所以你没有开启cssModules:true

无法引入css

ERROR Failed to compile with 1 errors 15:14:37

error in ./node_modules/react-quill/dist/quill.snow.css

Module parse failed: Unexpected token (1:0)
You may need an appropriate loader to handle this file type.

.ql-container {
| box-sizing: border-box;
| font-family: Helvetica, Arial, sans-serif;

@ ./pages/addblog/index.js 46:0-41
@ multi ./pages/addblog/index.js

我在工程中引入React-quill,工程无法引入quill的module中的css文件啊
求指点啊

yarn 报错

7785e5ef-856e-4bde-8e00-74a0c0d05c60
最近也在研究这个
看了一下你这里启动了 css module?
是如何吧antd 的样式排除在外的?

关于启动报错和mysql

yarn 加载依赖失败!

[2/4] 🚚  Fetching packages...
error [email protected]: The engine "node" is incompatible with this module. Expected version "0.8.x". Got "12.10.0"
error Found incompatible module.
info Visit https://yarnpkg.com/en/docs/cli/install for documentation about this command.

明显依赖版本未跟进导致,俺也懒得修了。。。

进来是想看,next js 怎么和mysql 在同一服务端口下结合运用的,结果发现你这根本就没有 mysql ,你搭了个 mysql 进来有什么意义?造成网络数据污染,好气噢.......

建议删掉 不存在的、与当前项目依赖无关的标签......

如果有mysql搭载的方法,请告知!

for example next.js + mongodb :

https://thecodebarbarian.com/building-a-nextjs-app-with-mongodb

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.