Repository: arefaslani/next-boilerplate Branch: master Commit: 7d9e5fb424e4 Files: 32 Total size: 19.6 KB Directory structure: gitextract_m99q8486/ ├── .babelrc ├── .eslintignore ├── .eslintrc.json ├── .flowconfig ├── .gitignore ├── .prettierignore ├── .storybook/ │ ├── config.js │ └── webpack.config.js ├── README.md ├── components/ │ └── NProgress/ │ └── index.js ├── next.config.js ├── package.json ├── pages/ │ ├── _app.js │ ├── _document.js │ ├── index.js │ ├── post.js │ ├── secret.js │ └── signin.js ├── postcss.config.js ├── routes.js ├── server/ │ └── index.js ├── services/ │ ├── api/ │ │ └── index.js │ └── auth/ │ ├── authenticate.js │ └── sign_in.js ├── static/ │ └── css/ │ └── nprogress.css ├── store/ │ ├── index.js │ ├── posts/ │ │ ├── actions.js │ │ ├── reducer.js │ │ └── sagas.js │ ├── reducer.js │ └── sagas.js └── stories/ └── index.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "presets": [ [ "next/babel", { "preset-env": { "modules": "commonjs" } } ] ], "plugins": [ [ "styled-components", { "ssr": true, "displayName": true, "preprocess": false } ], "transform-flow-strip-types", [ "module-resolver", { "root": ["./"] } ] ] } ================================================ FILE: .eslintignore ================================================ /.next/** /node_modules/** ================================================ FILE: .eslintrc.json ================================================ { "parser": "babel-eslint", "extends": [ "airbnb", "prettier", "plugin:flowtype/recommended" ], "plugins": [ "flowtype" ], "settings": { "import/resolver": { "babel-module": {} } }, "rules": { "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], "no-unused-vars": ["error", { "args": "none" }], "import/no-extraneous-dependencies": ["error", { "devDependencies": true }], "jsx-a11y/anchor-is-valid": ["error", { "components": ["Link"], "specialLink": ["route"], "aspects": ["invalidHref", "preferButton"] }], "import/extensions": "off", "import/no-unresolved": "off" } } ================================================ FILE: .flowconfig ================================================ [ignore] .*/node_modules/config-chain/test/broken.json .*/node_modules/npmconf/test/fixtures/package.json [include] [libs] [lints] [options] module.system.node.resolve_dirname=node_modules module.system.node.resolve_dirname=. module.file_ext=.css module.file_ext=.scss module.file_ext=.js module.file_ext=.jsx module.file_ext=.json [strict] ================================================ FILE: .gitignore ================================================ # See https://help.github.com/ignore-files/ for more about ignoring files. # dependencies /node_modules # testing /coverage # production /build /dist /.next # misc .DS_Store .env npm-debug.log* yarn-debug.log* yarn-error.log* scss/**/*.css ================================================ FILE: .prettierignore ================================================ .next/** node_modules/** ================================================ FILE: .storybook/config.js ================================================ import { configure } from "@storybook/react"; function loadStories() { require("../stories"); } configure(loadStories, module); ================================================ FILE: .storybook/webpack.config.js ================================================ // you can use this file to add your custom webpack plugins, loaders and anything you like. // This is just the basic way to add additional webpack configurations. // For more information refer the docs: https://storybook.js.org/configurations/custom-webpack-config // IMPORTANT // When you add this file, we won't add the default configurations which is similar // to "React Create App". This only has babel loader to load JavaScript. module.exports = { plugins: [ // your custom plugins ], module: { rules: [ // add your custom rules. ] } }; ================================================ FILE: README.md ================================================ # NEXT Boilerplate Lightweight Next boilerplate! ## Background We had tough times working with Next to make it our beloved framework. We opened issues (https://github.com/zeit/next.js/issues/3131), make pull requests and discuss a lot in the slack channel of NextJs. Unfortunately some of our must have features was not supported at that time. So we started creating a boilerplate that had all the features we wanted on top of Next. ## Features * **redux** for handling application state * **redux-saga** for handling async actions and side-effects * **next-routes** for handling dynamic routes * **axios** for making HTTP requests * **dotenv** for using environment variables * **express** as the server * **redux-devtools** in development * **redux-logger** in development for managing actions and state changes they cause * **universal-cookie-express** as a middleware for easily writing cookies * **compression** for compressing static assets * **babel-plugin-module-resolver** for importing modules related to the root directory * **prettier** and **eslint** configured with **airbnb**'s styleguide for formating code * **husky** and **lint-staged** for autoformatting code before commit * **styled-components** allows you to write actual CSS code to style your components. this boilerplate also includes **flow** and **storybook** that you can easily remove them if you don't like. For more information please read the [docs](https://arefaslani.github.io/next-boilerplate) ================================================ FILE: components/NProgress/index.js ================================================ import React from "react"; import Head from "next/head"; import NProgress from "nprogress"; import { Router } from "routes"; NProgress.configure({ showSpinner: false }); Router.onRouteChangeStart = url => { NProgress.start(); }; Router.onRouteChangeComplete = () => NProgress.done(); Router.onRouteChangeError = () => NProgress.done(); export default () => (
{/* Import CSS for nprogress */}
); ================================================ FILE: next.config.js ================================================ const webpack = require("webpack"); const { parsed: localEnv } = require("dotenv").config(); const withSourceMaps = require("@zeit/next-source-maps"); const withImages = require("next-images"); const withPlugins = require("next-compose-plugins"); const withBundleAnalyzer = require("@zeit/next-bundle-analyzer"); const plugins = [ withSourceMaps, withImages, [ withBundleAnalyzer, { analyzeServer: ["server", "both"].includes(process.env.BUNDLE_ANALYZE), analyzeBrowser: ["browser", "both"].includes(process.env.BUNDLE_ANALYZE), bundleAnalyzerConfig: { server: { analyzerMode: "static", reportFilename: "../server-analyze.html" }, browser: { analyzerMode: "static", reportFilename: "client-analyze.html" } } } ] ]; module.exports = withPlugins([...plugins], { webpack: (config, { dev, isServer }) => { const conf = config; // Fixes npm packages that depend on `fs` module conf.node = { fs: "empty" }; conf.plugins.push(new webpack.EnvironmentPlugin(localEnv)); return conf; } }); ================================================ FILE: package.json ================================================ { "name": "next-boilerplate", "version": "1.1.0", "license": "MIT", "scripts": { "dev": "node server", "build": "next build", "start": "NODE_ENV=production node server", "precommit": "lint-staged", "storybook": "start-storybook -p 6006", "build-storybook": "build-storybook" }, "lint-staged": { "*.js": [ "eslint --fix", "prettier-eslint --write", "git add" ] }, "dependencies": { "@zeit/next-bundle-analyzer": "^0.1.1", "@zeit/next-source-maps": "^0.0.2", "axios": "^0.18.0", "babel-core": "^7.0.0-bridge.0", "babel-eslint": "^8.2.3", "babel-plugin-module-resolver": "^3.1.1", "babel-plugin-styled-components": "^1.5.1", "compression": "^1.7.2", "dotenv": "^6.0.0", "express": "^4.16.3", "next": "^6.0.3", "next-compose-plugins": "^2.1.1", "next-images": "^0.10.5", "next-redux-saga": "^2.0.1", "next-redux-wrapper": "^1.3.5", "next-routes": "^1.4.2", "nprogress": "^0.2.0", "prop-types": "^15.6.1", "react": "^16.4.1", "react-dom": "^16.4.1", "react-redux": "^5.0.7", "redux": "^4.0.0", "redux-devtools-extension": "^2.13.2", "redux-logger": "^3.0.6", "redux-saga": "^0.16.0", "styled-components": "^3.3.2", "universal-cookie-express": "^2.1.5", "webpack": "^3.11.0" }, "devDependencies": { "@storybook/addon-actions": "^3.4.7", "@storybook/addon-links": "^3.4.7", "@storybook/react": "^3.4.7", "autoprefixer": "^8.6.2", "babel-plugin-transform-flow-strip-types": "^6.22.0", "eslint": "^4.19.1", "eslint-config-airbnb": "^16.1.0", "eslint-config-prettier": "^2.9.0", "eslint-import-resolver-babel-module": "^4.0.0", "eslint-plugin-flowtype": "^2.49.3", "eslint-plugin-import": "^2.12.0", "eslint-plugin-jsx-a11y": "^6.0.3", "eslint-plugin-react": "^7.9.1", "flow-bin": "^0.74.0", "husky": "^0.14.3", "lint-staged": "^7.2.0", "node-sass": "^4.9.0", "postcss-loader": "^2.1.5", "prettier": "^1.13.5", "prettier-eslint-cli": "^4.7.1", "styled-jsx-css-loader": "^0.3.0", "webpack-bundle-analyzer": "^2.13.1" } } ================================================ FILE: pages/_app.js ================================================ import React from "react"; import App, { Container } from "next/app"; export default class MyApp extends App { static async getInitialProps({ Component, ctx }) { let pageProps = {}; if (Component.getInitialProps) { pageProps = await Component.getInitialProps(ctx); } return { pageProps }; } createUrl = router => { // This is to make sure we don't references the router object at call time const { pathname, asPath, query } = router; return { get query() { return query; }, get pathname() { return pathname; }, get asPath() { return asPath; }, back: () => { router.back(); }, push: (url, as) => router.push(url, as), pushTo: (href, as) => { const pushRoute = as ? href : null; const pushUrl = as || href; return router.push(pushRoute, pushUrl); }, replace: (url, as) => router.replace(url, as), replaceTo: (href, as) => { const replaceRoute = as ? href : null; const replaceUrl = as || href; return router.replace(replaceRoute, replaceUrl); } }; }; render() { const { Component, pageProps, router } = this.props; const url = this.createUrl(router); return ( ); } } ================================================ FILE: pages/_document.js ================================================ import React from "react"; import Document, { Head, Main, NextScript } from "next/document"; import { ServerStyleSheet } from "styled-components"; export default class MyDocument extends Document { static getInitialProps({ renderPage }) { const sheet = new ServerStyleSheet(); const page = renderPage(App => props => sheet.collectStyles() ); const styleTags = sheet.getStyleElement(); return { ...page, styleTags }; } render() { return ( My page {this.props.styleTags}
); } } ================================================ FILE: pages/index.js ================================================ import React, { Component } from "react"; import { bindActionCreators } from "redux"; import withRedux from "next-redux-wrapper"; import withReduxSaga from "next-redux-saga"; import Head from "next/head"; import PropTypes from "prop-types"; import styled from "styled-components"; import Store from "store"; import { fetchPosts } from "store/posts/actions"; import { Link } from "routes"; import NProgress from "components/NProgress"; const H1 = styled.h1` color: #458542; `; class PostsIndex extends Component { static getInitialProps({ store }) { store.dispatch(fetchPosts()); } render() { const { posts, fetchPostsAction } = this.props; return (
Posts Index

Posts

{posts.length > 0 && posts.map(post => (

{post.title}

{post.body}

))}
); } } PostsIndex.propTypes = { posts: PropTypes.arrayOf(PropTypes.object).isRequired, fetchPostsAction: PropTypes.func.isRequired }; const mapStateToProps = state => ({ posts: state.posts.list }); const mapDispatchToProps = dispatch => bindActionCreators( { fetchPostsAction: fetchPosts }, dispatch ); export default withRedux(Store, mapStateToProps, mapDispatchToProps)( withReduxSaga(PostsIndex) ); ================================================ FILE: pages/post.js ================================================ import React, { Component } from "react"; import { bindActionCreators } from "redux"; import withRedux from "next-redux-wrapper"; import withReduxSaga from "next-redux-saga"; import Head from "next/head"; import PropTypes from "prop-types"; import { fetchPost } from "store/posts/actions"; import Store from "store"; import { Router } from "routes"; import NProgress from "components/NProgress"; class Post extends Component { static getInitialProps({ query, store }) { const { id } = query; store.dispatch(fetchPost(id)); return { id }; } render() { const { post } = this.props; return (
{post && {post.title}} {post && (

{post.title}

{post.body}

)}
); } } Post.propTypes = { post: PropTypes.shape({}).isRequired }; const mapDispatchToProps = dispatch => bindActionCreators( { fetchPost }, dispatch ); const mapStateToProps = state => ({ post: state.posts.currentPost }); export default withRedux(Store, mapStateToProps, mapDispatchToProps)( withReduxSaga(Post) ); ================================================ FILE: pages/secret.js ================================================ import React, { Component } from "react"; import authenticate from "services/auth/authenticate"; export default class Secret extends Component { static getInitialProps(ctx) { const user = authenticate(ctx); return { user }; } render() { return
Secret Page
; } } ================================================ FILE: pages/signin.js ================================================ import React, { Component } from "react"; import signIn from "services/auth/sign_in"; export default class Signin extends Component { static getInitialProps(ctx) { signIn(ctx); return {}; } render() { return
; } } ================================================ FILE: postcss.config.js ================================================ module.exports = { plugins: [] }; ================================================ FILE: routes.js ================================================ const routes = require("next-routes")(); module.exports = routes; routes.add("about").add("post", "/posts/:id"); ================================================ FILE: server/index.js ================================================ // server.js const next = require("next"); const cookiesMiddleware = require("universal-cookie-express"); const compression = require("compression"); const routes = require("../routes"); const app = next({ dev: process.env.NODE_ENV !== "production" }); const handler = routes.getRequestHandler(app); // With express const express = require("express"); app.prepare().then(() => { express() .use(cookiesMiddleware()) .use(compression()) .use(handler) .listen(3000); }); ================================================ FILE: services/api/index.js ================================================ import axios from "axios"; const facade = {}; const api = axios.create({ baseURL: "https://jsonplaceholder.typicode.com" }); facade.request = config => api.request(config); ["get", "head"].forEach(method => { facade[method] = (url, config) => facade.request({ ...config, method, url }); }); ["delete", "post", "put", "patch"].forEach(method => { facade[method] = (url, data, config) => facade.request({ ...config, method, url, data }); }); class API { static fetchAllPosts() { return facade.get("/posts"); } static fetchPost(id) { return facade.get(`/posts/${id}`); } } export default API; ================================================ FILE: services/auth/authenticate.js ================================================ export default ctx => { const { req, res } = ctx; const token = req.universalCookies.get("token"); if (!token) { return res.redirect("/"); } return { username: "testUser" }; }; ================================================ FILE: services/auth/sign_in.js ================================================ export default ctx => { const { req, res } = ctx; req.universalCookies.set("token", "test"); res.redirect("/"); }; ================================================ FILE: static/css/nprogress.css ================================================ /* Make clicks pass-through */ #nprogress { pointer-events: none; } #nprogress .bar { background: #29d; position: fixed; z-index: 1031; top: 0; left: 0; width: 100%; height: 2px; } /* Fancy blur effect */ #nprogress .peg { display: block; position: absolute; right: 0px; width: 100px; height: 100%; box-shadow: 0 0 10px #29d, 0 0 5px #29d; opacity: 1.0; -webkit-transform: rotate(3deg) translate(0px, -4px); -ms-transform: rotate(3deg) translate(0px, -4px); transform: rotate(3deg) translate(0px, -4px); } /* Remove these to get rid of the spinner */ #nprogress .spinner { display: block; position: fixed; z-index: 1031; top: 15px; right: 15px; } #nprogress .spinner-icon { width: 18px; height: 18px; box-sizing: border-box; border: solid 2px transparent; border-top-color: #29d; border-left-color: #29d; border-radius: 50%; -webkit-animation: nprogress-spinner 400ms linear infinite; animation: nprogress-spinner 400ms linear infinite; } .nprogress-custom-parent { overflow: hidden; position: relative; } .nprogress-custom-parent #nprogress .spinner, .nprogress-custom-parent #nprogress .bar { position: absolute; } @-webkit-keyframes nprogress-spinner { 0% { -webkit-transform: rotate(0deg); } 100% { -webkit-transform: rotate(360deg); } } @keyframes nprogress-spinner { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } ================================================ FILE: store/index.js ================================================ import { createStore, applyMiddleware } from "redux"; import { createLogger } from "redux-logger"; import { composeWithDevTools } from "redux-devtools-extension/developmentOnly"; import createSagaMiddleware from "redux-saga"; import rootSaga from "store/sagas"; import reducer from "store/reducer"; // Setup const middleWare = []; // Saga Middleware const sagaMiddleware = createSagaMiddleware(); middleWare.push(sagaMiddleware); // Logger Middleware. This always has to be last const loggerMiddleware = createLogger({ predicate: () => process.env.NODE_ENV === "development" }); middleWare.push(loggerMiddleware); const createStoreWithMiddleware = composeWithDevTools( applyMiddleware(...middleWare) )(createStore); const makeStore = (initialState, options) => { const store = createStoreWithMiddleware(reducer, initialState); store.runSagaTask = () => { store.sagaTask = sagaMiddleware.run(rootSaga); }; // run the rootSaga initially store.runSagaTask(); return store; }; export default makeStore; ================================================ FILE: store/posts/actions.js ================================================ export const FETCH_POSTS = "FETCH_POSTS"; export const FETCH_POSTS_SUCCEEDED = "FETCH_POSTS_SUCCEEDED"; export const FETCH_POST = "FETCH_POST"; export const FETCH_POST_SUCCEEDED = "FETCH_POST_SUCCEEDED"; export function fetchPosts() { return { type: FETCH_POSTS }; } export function fetchPost(id) { return { type: FETCH_POST, payload: id }; } ================================================ FILE: store/posts/reducer.js ================================================ import { FETCH_POSTS_SUCCEEDED, FETCH_POST_SUCCEEDED } from "store/posts/actions"; export default function(state = {}, action) { switch (action.type) { case FETCH_POSTS_SUCCEEDED: return { ...state, list: action.payload }; case FETCH_POST_SUCCEEDED: return { ...state, currentPost: action.payload }; default: return state; } } ================================================ FILE: store/posts/sagas.js ================================================ import { takeLatest, fork, call, put } from "redux-saga/effects"; import api from "services/api"; import { FETCH_POSTS, FETCH_POSTS_SUCCEEDED, FETCH_POST, FETCH_POST_SUCCEEDED } from "store/posts/actions"; function* fetchPosts(action) { const posts = yield call(api.fetchAllPosts); yield put({ type: FETCH_POSTS_SUCCEEDED, payload: posts.data }); } function* fetchPost({ payload }) { const post = yield call(api.fetchPost, payload); yield put({ type: FETCH_POST_SUCCEEDED, payload: post.data }); } function* watchFetchPosts() { yield takeLatest(FETCH_POSTS, fetchPosts); } function* watchFetchPost() { yield takeLatest(FETCH_POST, fetchPost); } export default function* postsSagas() { yield fork(watchFetchPosts); yield fork(watchFetchPost); } ================================================ FILE: store/reducer.js ================================================ import { combineReducers } from "redux"; import postsReducer from "store/posts/reducer"; const reducers = { posts: postsReducer }; export default combineReducers(reducers); ================================================ FILE: store/sagas.js ================================================ import { all } from "redux-saga/effects"; import postSagas from "store/posts/sagas"; export default function* rootSaga(services = {}) { yield all([postSagas()]); } ================================================ FILE: stories/index.js ================================================ import React from "react"; import { storiesOf } from "@storybook/react"; import { action } from "@storybook/addon-actions"; import { linkTo } from "@storybook/addon-links"; import { Button, Welcome } from "@storybook/react/demo"; storiesOf("Welcome", module).add("to Storybook", () => ( )); storiesOf("Button", module) .add("with text", () => ( )) .add("with some emoji", () => ( ));