├── .gitignore ├── README.md ├── components └── header.js ├── images ├── api-key-set.png ├── blogid.png ├── create-new-blog.png ├── env-file.png └── get-key.png ├── next.config.js ├── package.json ├── pages ├── index.js └── post.js ├── server.js └── shared └── MUI ├── theme.js └── withMUI.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | .next 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Build a Server-rendered ReactJS Application with Next.js 2 | 3 | ## Description 4 | 5 | In this course we we’ll see just how quickly next.js makes the process of building server-rendered ReactJS applications by creating and deploying an application that loads blog posts from the Google Blogger API. Along the way we’ll learn about many of the amazing features Next.js provides for us out of the box, such as route prefetching and code-splitting, thus allowing us to spend more time developing and virtually no time setting up our environment. 6 | 7 | Additionally, we’ll learn about the core concepts behind the framework and see how we can leverage them to create dynamic routes and integrate Material-UI on the server. We won’t have to worry about using any specific architecture to handle state, instead we will just pass our data as ReactJS props using Next.js’ getInitialProps lifecycle hook. Throughout this course we will see why Next.js has gained such an amazing reputation as a “minimalist framework” by supplying users with “pretty” error messages. Once finished, we’ll deploy our application to a live URL using the now-cli npm module. 8 | 9 | This course requires an account with Google Blogger which is described below. 10 | 11 | ## Set up Google's Blogger API 12 | 13 | Visit the [Blogger homepage](https://www.blogger.com/about/). 14 | 15 | Create an account or sign in. 16 | 17 | Create a blog you want to use with your application. The name and url don't really matter as you will be using the blog ID. 18 | 19 | ![](images/create-new-blog.png) 20 | 21 | #### Grab the blogID from the url: 22 | 23 | ![](images/blogid.png) 24 | 25 | Now that the blog is set up, you need to create a key for the blog. This can be done [here](https://developers.google.com/blogger/docs/3.0/using#APIKey) under `Acquiring and using an API key` 26 | 27 | 28 | ![](images/get-key.png) 29 | 30 | Agree to terms and services. 31 | 32 | Copy the key that it provides you: 33 | 34 | ![](images/api-key-set.png) 35 | 36 | And finally, set both the `BLOGGER_URL` and `API_KEY` respectively: 37 | 38 | ![](images/env-file.png) 39 | 40 | -------------------------------------------------------------------------------- /components/header.js: -------------------------------------------------------------------------------- 1 | import AppBar from 'material-ui/AppBar'; 2 | 3 | const Header = ({ title = 'Next.js blogging application'}) => 4 | 5 | 6 | export default Header; 7 | -------------------------------------------------------------------------------- /images/api-key-set.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgrecojs/nextjs-blogger/78099575b721231097ed984c0d87e00e21a029a3/images/api-key-set.png -------------------------------------------------------------------------------- /images/blogid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgrecojs/nextjs-blogger/78099575b721231097ed984c0d87e00e21a029a3/images/blogid.png -------------------------------------------------------------------------------- /images/create-new-blog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgrecojs/nextjs-blogger/78099575b721231097ed984c0d87e00e21a029a3/images/create-new-blog.png -------------------------------------------------------------------------------- /images/env-file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgrecojs/nextjs-blogger/78099575b721231097ed984c0d87e00e21a029a3/images/env-file.png -------------------------------------------------------------------------------- /images/get-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgrecojs/nextjs-blogger/78099575b721231097ed984c0d87e00e21a029a3/images/get-key.png -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | require('dotenv').config(); 3 | 4 | module.exports = { 5 | webpack: config => { 6 | config.plugins.push( 7 | new webpack.EnvironmentPlugin(['BLOGGER_URL', 'API_KEY']) 8 | ); 9 | return config; 10 | } 11 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-course", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "node server.js", 8 | "start": "NODE_ENV=production node server.js", 9 | "build": "next build" 10 | }, 11 | "keywords": [], 12 | "author": "Thomas Greco", 13 | "license": "ISC", 14 | "dependencies": { 15 | "dotenv": "^4.0.0", 16 | "express": "^4.16.2", 17 | "material-ui": "^0.19.4", 18 | "next": "^4.1.4", 19 | "react": "^16.2.0", 20 | "react-dom": "^16.2.0", 21 | "react-tap-event-plugin": "^3.0.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import Header from '../components/header'; 2 | import withMui from '../shared/MUI/withMUI'; 3 | import 'isomorphic-fetch'; 4 | import { Card, CardHeader, CardText } from 'material-ui/Card'; 5 | import RaisedButton from 'material-ui/RaisedButton'; 6 | import Link from 'next/link'; 7 | 8 | const Index = ({ posts }) => 9 |
10 | 19 |
20 | { 21 | posts.map(x => 22 | 23 | 24 | 25 | 26 | 27 | 28 | Click to view post! 29 | 30 | 31 | 32 | 33 | 34 | ) 35 | } 36 |
; 37 | 38 | Index.getInitialProps = async () => { 39 | const response = await fetch(`${process.env.BLOGGER_URL}?key=${process.env.API_KEY}`); 40 | const data = await response.json(); 41 | return { posts: data.items } 42 | } 43 | 44 | export default withMui(Index); 45 | -------------------------------------------------------------------------------- /pages/post.js: -------------------------------------------------------------------------------- 1 | import Header from '../components/header'; 2 | import withMui from '../shared/MUI/withMUI'; 3 | import { Card, CardHeader, CardText } from 'material-ui/Card'; 4 | import 'isomorphic-fetch'; 5 | import RaisedButton from 'material-ui/RaisedButton'; 6 | import Link from 'next/link'; 7 | 8 | const Post = ({ title, content }) => 9 |
10 |
11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | Go back to blog! 19 | 20 | 21 | 22 | 23 | 24 |
; 25 | 26 | Post.getInitialProps = async ({ query: { id }}) => { 27 | const response = await fetch(`${process.env.BLOGGER_URL}/${id}?key=${process.env.API_KEY}`); 28 | const data = await response.json(); 29 | const title = data.title; 30 | const content = data.content; 31 | 32 | return { title, content } 33 | }; 34 | 35 | export default withMui(Post); 36 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const next = require('next') 3 | const port = parseInt(process.env.PORT, 10) || 3000 4 | const dev = process.env.NODE_ENV !== 'production' 5 | const app = next({ dev }) 6 | const handle = app.getRequestHandler(); 7 | 8 | app.prepare().then(() => { 9 | const server = express(); 10 | 11 | server.get('/blog', (req, res) => app.render(req, res, '/')); 12 | 13 | server.get('/', (req, res) => res.redirect(301, '/blog')); 14 | 15 | server.get('/blog/:id', (req, res) => { 16 | return app.render(req, res, '/post', Object.assign({id: req.params.id}, req.query)) 17 | }); 18 | 19 | server.get('/post', (req, res) => { 20 | if(req.query.id) return res.redirect(`/blog/${req.query.id}`); 21 | res.redirect(301, '/blog'); 22 | }); 23 | 24 | server.get('/*', (req, res) => handle(req, res)); 25 | 26 | server.listen(port, err => { 27 | if(err) throw err 28 | console.log(`> Read on http://localhost:${port}`); 29 | }) 30 | }); 31 | 32 | -------------------------------------------------------------------------------- /shared/MUI/theme.js: -------------------------------------------------------------------------------- 1 | export const PRIMARY_COLOR = '#569cb7'; 2 | export const PRIMARY_COLOR_TWO = '#88cde9'; 3 | export const PRIMARY_COLOR_THREE = '#1e6e87'; 4 | export const ACCENT_COLOR_ONE = '#556cb7'; 5 | export const ACCENT_COLOR_TWO = '#889aea'; 6 | export const ACCENT_COLOR_THREE = '#204287'; 7 | -------------------------------------------------------------------------------- /shared/MUI/withMUI.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; 3 | import getMuiTheme from 'material-ui/styles/getMuiTheme'; 4 | import injectTapEventPlugin from 'react-tap-event-plugin'; 5 | import Head from 'next/head'; 6 | import { 7 | PRIMARY_COLOR, 8 | PRIMARY_COLOR_TWO, 9 | PRIMARY_COLOR_THREE, 10 | ACCENT_COLOR_ONE, 11 | ACCENT_COLOR_TWO, 12 | ACCENT_COLOR_THREE 13 | } from './theme'; 14 | 15 | try { 16 | injectTapEventPlugin(); 17 | } catch (e) { 18 | // Can only be called once per application lifecycle 19 | } 20 | 21 | const withMaterialUI = ComposedComponent => { 22 | class HOC extends Component { 23 | 24 | static async getInitialProps(ctx) { 25 | const { req } = ctx; 26 | const userAgent = req ? req.headers['user-agent'] : navigator.userAgent; 27 | const subProps = await ComposedComponent.getInitialProps(ctx) 28 | 29 | return { 30 | ...subProps, 31 | userAgent 32 | }; 33 | } 34 | 35 | render() { 36 | const { userAgent } = this.props; 37 | const Lato = 'lato, sans-serif'; 38 | const muiTheme = getMuiTheme( 39 | { 40 | fontFamily: Lato, 41 | palette: { 42 | primary1Color: PRIMARY_COLOR, 43 | primary2Color: PRIMARY_COLOR_TWO, 44 | primary3Color: PRIMARY_COLOR_THREE, 45 | accent1Color: ACCENT_COLOR_ONE, 46 | accent2Color: ACCENT_COLOR_TWO, 47 | accent3Color: ACCENT_COLOR_THREE 48 | }, 49 | appBar: { 50 | height: 50 51 | } 52 | }, 53 | { 54 | userAgent 55 | } 56 | ); 57 | return ( 58 |
59 | 60 | Nextjs Blogger 61 | 62 | 63 | 64 | 65 | 66 | 67 |
68 | ) 69 | } 70 | } 71 | return HOC; 72 | } 73 | 74 | export default withMaterialUI; --------------------------------------------------------------------------------