├── .codeclimate.yml
├── .babelrc
├── logo.png
├── .gitignore
├── actions
├── index.js
└── todo.js
├── static
├── img
│ ├── favicon.ico
│ ├── android-chrome-144x144.png
│ ├── android-chrome-192x192.png
│ ├── apple-touch-icon-120x120.png
│ ├── apple-touch-icon-152x152.png
│ └── splashscreen-icon-384x384.png
└── manifest.json
├── .prettierrc
├── reducers
├── index.js
└── todos.js
├── .gitpod.yml
├── .editorconfig
├── components
├── TodoItem.js
├── Fork.js
└── Todo.js
├── .travis.yml
├── utils
├── store.js
├── offline.js
└── sw.js
├── package.json
├── server.js
├── pages
├── index.js
├── _document.js
└── _app.js
├── next.config.js
├── license
└── readme.md
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | Javascript: true
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [ "next/babel" ]
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitpod-io/NextSimpleStarter/HEAD/logo.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | static/sw.js
3 | .next
4 | *-error.log
5 | *lock.json
6 | yarn.lock
7 |
--------------------------------------------------------------------------------
/actions/index.js:
--------------------------------------------------------------------------------
1 | export const ADD_TODO = 'ADD_TODO'
2 | export const REMOVE_TODO = 'REMOVE_TODO'
3 |
--------------------------------------------------------------------------------
/static/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitpod-io/NextSimpleStarter/HEAD/static/img/favicon.ico
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "singleQuote": true,
4 | "useTabs": true,
5 | "tabWidth": 2
6 | }
7 |
--------------------------------------------------------------------------------
/static/img/android-chrome-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitpod-io/NextSimpleStarter/HEAD/static/img/android-chrome-144x144.png
--------------------------------------------------------------------------------
/static/img/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitpod-io/NextSimpleStarter/HEAD/static/img/android-chrome-192x192.png
--------------------------------------------------------------------------------
/static/img/apple-touch-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitpod-io/NextSimpleStarter/HEAD/static/img/apple-touch-icon-120x120.png
--------------------------------------------------------------------------------
/static/img/apple-touch-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitpod-io/NextSimpleStarter/HEAD/static/img/apple-touch-icon-152x152.png
--------------------------------------------------------------------------------
/static/img/splashscreen-icon-384x384.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitpod-io/NextSimpleStarter/HEAD/static/img/splashscreen-icon-384x384.png
--------------------------------------------------------------------------------
/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 |
3 | import todos from './todos'
4 |
5 | export default combineReducers({ todos })
6 |
--------------------------------------------------------------------------------
/.gitpod.yml:
--------------------------------------------------------------------------------
1 | ports:
2 | - port: 3000
3 | onOpen: open-preview
4 | tasks:
5 | - init: yarn
6 | command: yarn dev
7 | github:
8 | prebuilds:
9 | pullRequestsFromForks: true
10 |
--------------------------------------------------------------------------------
/actions/todo.js:
--------------------------------------------------------------------------------
1 | import { ADD_TODO, REMOVE_TODO } from './'
2 |
3 | export function addTodo(text) {
4 | return {
5 | type: ADD_TODO,
6 | text
7 | }
8 | }
9 |
10 | export function removeTodo(todo) {
11 | return {
12 | type: REMOVE_TODO,
13 | todo
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = tab
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [{package.json,.*rc,*.yml}]
12 | indent_style = space
13 | indent_size = 2
14 |
15 | [*.md]
16 | trim_trailing_whitespace = false
--------------------------------------------------------------------------------
/components/TodoItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const TodoItem = ({ todo, remove }) => {
4 | return (
5 |
6 | {' '}
13 | {todo.text}
14 |
15 | )
16 | }
17 |
18 | export default TodoItem
19 |
--------------------------------------------------------------------------------
/reducers/todos.js:
--------------------------------------------------------------------------------
1 | import { ADD_TODO, REMOVE_TODO } from '../actions'
2 |
3 | export default function(state = [], action) {
4 | const { type, text, todo } = action
5 |
6 | switch (type) {
7 | case ADD_TODO:
8 | return [
9 | ...state,
10 | {
11 | id: Math.random()
12 | .toString(36)
13 | .substring(2),
14 | text
15 | }
16 | ]
17 | case REMOVE_TODO:
18 | return state.filter(i => i !== todo)
19 | default:
20 | return state
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 |
3 | language: node_js
4 |
5 | node_js:
6 | - '6'
7 | - '10'
8 |
9 | install:
10 | - npm install
11 |
12 | cache:
13 | directories:
14 | - node_modules
15 |
16 | # Necessary to compile native modules for io.js v3 or Node.js v4
17 | env:
18 | - CXX=g++-4.8
19 |
20 | # Necessary to compile native modules for io.js v3 or Node.js v4
21 | addons:
22 | apt:
23 | sources:
24 | - ubuntu-toolchain-r-test
25 | packages:
26 | - g++-4.8
27 |
--------------------------------------------------------------------------------
/utils/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, compose, applyMiddleware } from 'redux'
2 |
3 | import rootReducer from '../reducers'
4 |
5 | const enhancers = compose(
6 | typeof window !== 'undefined' && process.env.NODE_ENV !== 'production'
7 | ? window.devToolsExtension && window.devToolsExtension()
8 | : f => f
9 | )
10 |
11 | const createStoreWithMiddleware = applyMiddleware()(createStore)
12 |
13 | export default initialState =>
14 | createStoreWithMiddleware(rootReducer, initialState, enhancers)
15 |
--------------------------------------------------------------------------------
/components/Fork.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Fork = ({ stars }) => (
4 |
23 | )
24 |
25 | export default Fork
26 |
--------------------------------------------------------------------------------
/utils/offline.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Registers our Service Worker on the site
3 | * Need more? check out:
4 | * https://github.com/GoogleChrome/sw-precache/blob/master/demo/app/js/service-worker-registration.js
5 | */
6 |
7 | if (
8 | process.env.NODE_ENV === 'production' &&
9 | typeof window !== 'undefined' &&
10 | 'serviceWorker' in navigator
11 | ) {
12 | navigator.serviceWorker
13 | .register('/sw.js')
14 | .then(function(reg) {
15 | console.log('Service worker registered (0-0) ')
16 | })
17 | .catch(function(e) {
18 | console.error('Error during service worker registration:', e)
19 | })
20 | }
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next-simple-starter",
3 | "version": "1.0.0",
4 | "repository": {
5 | "url": "https://github.com/ooade/NextSimpleStarter"
6 | },
7 | "license": "MIT",
8 | "dependencies": {
9 | "isomorphic-fetch": "^2.2.1",
10 | "next": "^8.0.0",
11 | "next-redux-wrapper": "^2.0.0",
12 | "react": "^16.8.1",
13 | "react-dom": "^16.8.1",
14 | "react-redux": "^6.0.0",
15 | "redux": "^3.7.2"
16 | },
17 | "scripts": {
18 | "dev": "node server",
19 | "build": "next build",
20 | "start": "NODE_ENV=production node server"
21 | },
22 | "devDependencies": {
23 | "workbox-webpack-plugin": "^4.0.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const { createServer } = require('http')
2 | const path = require('path')
3 | const next = require('next')
4 |
5 | const dev = process.env.NODE_ENV !== 'production'
6 | const app = next({ dir: '.', dev })
7 | const handle = app.getRequestHandler()
8 |
9 | const PORT = process.env.PORT || 3000
10 |
11 | app.prepare().then(_ => {
12 | const server = createServer((req, res) => {
13 | if (req.url === '/sw.js' || req.url.startsWith('/precache-manifest')) {
14 | app.serveStatic(req, res, path.join(__dirname, '.next', req.url))
15 | } else {
16 | handle(req, res)
17 | }
18 | })
19 |
20 | server.listen(PORT, err => {
21 | if (err) throw err
22 |
23 | console.log(`> App running on port ${PORT}`)
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import 'isomorphic-fetch'
2 | import React from 'react'
3 | import { connect } from 'react-redux'
4 |
5 | import Fork from '../components/Fork'
6 | import Todo from '../components/Todo'
7 |
8 | // Port in to using useState hooks, if you need state
9 | const Index = ({ stars }) => (
10 |
16 | )
17 |
18 | Index.getInitialProps = async({ store }) => {
19 | // Adding a default/initialState can be done as follows:
20 | // store.dispatch({ type: 'ADD_TODO', text: 'It works!' });
21 | const res = await fetch(
22 | 'https://api.github.com/repos/ooade/NextSimpleStarter'
23 | )
24 | const json = await res.json()
25 | return { stars: json.stargazers_count }
26 | }
27 |
28 | export default connect()(Index)
29 |
--------------------------------------------------------------------------------
/static/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "NextSimpleStarter",
3 | "short_name": "Next PWA",
4 | "icons": [
5 | {
6 | "src": "img/apple-touch-icon-120x120.png",
7 | "sizes": "120x120",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "img/apple-touch-icon-152x152.png",
12 | "sizes": "152x152",
13 | "type": "image/png"
14 | },
15 | {
16 | "src": "img/android-chrome-144x144.png",
17 | "sizes": "144x144",
18 | "type": "image/png"
19 | },
20 | {
21 | "src": "img/android-chrome-192x192.png",
22 | "sizes": "192x192",
23 | "type": "image/png"
24 | },
25 | {
26 | "src": "img/splashscreen-icon-384x384.png",
27 | "sizes": "384x384",
28 | "type": "image/png"
29 | }
30 | ],
31 | "start_url": "/",
32 | "display": "standalone",
33 | "theme_color": "#673ab7",
34 | "background_color": "#EEE"
35 | }
36 |
--------------------------------------------------------------------------------
/pages/_document.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Document, { Head, Main, NextScript } from 'next/document'
3 |
4 | export default class MyDocument extends Document {
5 | render() {
6 | return (
7 |
8 |
9 |
13 |
14 |
15 |
16 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | )
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const WorkboxPlugin = require('workbox-webpack-plugin')
3 |
4 | module.exports = {
5 | webpack: (config, { buildId, dev }) => {
6 | /**
7 | * Install and Update our Service worker
8 | * on our main entry file :)
9 | * Reason: https://github.com/ooade/NextSimpleStarter/issues/32
10 | */
11 | const oldEntry = config.entry
12 |
13 | config.entry = () =>
14 | oldEntry().then(entry => {
15 | entry['main.js'] &&
16 | entry['main.js'].push(path.resolve('./utils/offline'))
17 | return entry
18 | })
19 |
20 | /* Enable only in Production */
21 | if (!dev) {
22 | // Service Worker
23 |
24 | config.plugins.push(
25 | new WorkboxPlugin.InjectManifest({
26 | swSrc: path.join(__dirname, 'utils', 'sw.js'),
27 | swDest: path.join(__dirname, '.next', 'sw.js'),
28 | globDirectory: __dirname,
29 | globPatterns: [
30 | 'static/**/*.{png,jpg,ico}' // Precache all static assets by default
31 | ]
32 | })
33 | )
34 | }
35 |
36 | return config
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Head from 'next/head'
3 | import { Provider } from 'react-redux'
4 | import App, { Container } from 'next/app'
5 | import withRedux from 'next-redux-wrapper'
6 |
7 | import initStore from '../utils/store'
8 |
9 | /* debug to log how the store is being used */
10 | export default withRedux(initStore, {
11 | debug: typeof window !== 'undefined' && process.env.NODE_ENV !== 'production'
12 | })(
13 | class MyApp extends App {
14 | static async getInitialProps({ Component, ctx }) {
15 | return {
16 | pageProps: {
17 | // Call page-level getInitialProps
18 | ...(Component.getInitialProps
19 | ? await Component.getInitialProps(ctx)
20 | : {})
21 | }
22 | }
23 | }
24 |
25 | render() {
26 | const { Component, pageProps, store } = this.props
27 | return (
28 |
29 |
30 | Todo App
31 |
32 |
33 |
34 |
35 |
36 | )
37 | }
38 | }
39 | )
40 |
--------------------------------------------------------------------------------
/license:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Ademola Adegbuyi
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/utils/sw.js:
--------------------------------------------------------------------------------
1 | workbox.core.setCacheNameDetails({ prefix: 'next-ss' })
2 |
3 | workbox.skipWaiting()
4 | workbox.clientsClaim()
5 |
6 | workbox.precaching.suppressWarnings()
7 | /**
8 | * Ignore the non-important files added as a result of
9 | * webpack's publicPath thingy, for now...
10 | */
11 | // workbox.precaching.precacheAndRoute(self.__precacheManifest, {})
12 |
13 | /**
14 | * You can read about Cache Strategies here
15 | * (https://developers.google.com/web/tools/workbox/modules/workbox-strategies)
16 | */
17 |
18 | workbox.precaching.precacheAndRoute(
19 | self.__precacheManifest.filter(
20 | m =>
21 | !m.url.startsWith('bundles/') &&
22 | !m.url.startsWith('static/commons') &&
23 | m.url !== 'build-manifest.json'
24 | ),
25 | {}
26 | )
27 |
28 | workbox.routing.registerRoute(
29 | /[.](png|jpg|css)/,
30 | workbox.strategies.cacheFirst({
31 | cacheName: 'assets-cache',
32 | cacheableResponse: {
33 | statuses: [0, 200]
34 | }
35 | }),
36 | 'GET'
37 | )
38 |
39 | workbox.routing.registerRoute(
40 | /^https:\/\/code\.getmdl\.io.*/,
41 | workbox.strategies.cacheFirst({
42 | cacheName: 'lib-cache'
43 | }),
44 | 'GET'
45 | )
46 |
47 | // Fetch the root route as fast as possible
48 | workbox.routing.registerRoute(
49 | '/',
50 | workbox.strategies.staleWhileRevalidate({
51 | cacheName: 'root'
52 | }),
53 | 'GET'
54 | )
55 |
56 | workbox.routing.registerRoute(
57 | /^http.*/,
58 | workbox.strategies.networkFirst({
59 | cacheName: 'http-cache'
60 | }),
61 | 'GET'
62 | )
63 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
Simple NextJS PWA boilerplate.
5 |
6 |
7 | ## Contents
8 |
9 | - [Installation](#installation)
10 | - [Development Workflow](#development-workflow)
11 | - [Deployment](#deployment)
12 |
13 | ### Installation
14 |
15 | [](https://gitpod.io/#https://github.com/gitpod-io/NextSimpleStarter)
16 |
17 | ### Development Workflow
18 | Start a live-reload development server:
19 | ```sh
20 | yarn dev
21 | ```
22 | or
23 | ```sh
24 | npm run dev
25 | ```
26 |
27 | Generate a production build:
28 | ```sh
29 | yarn build
30 | ```
31 | or
32 | ```sh
33 | npm run build
34 | ```
35 | ### Deployment
36 | [](https://deploy.now.sh/?repo=https://github.com/ooade/NextSimpleStarter)
37 |
38 |
39 | heroku
40 | Just follow Mars's Guide and you're good to go :clap:
41 |
42 |
43 | ### Contribution
44 | I'm open to contributions & suggestions in making this a lot better :hand:
45 |
46 | ### License
47 | MIT
48 |
--------------------------------------------------------------------------------
/components/Todo.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import { connect } from 'react-redux'
3 |
4 | import { addTodo, removeTodo } from '../actions/todo'
5 | import TodoItem from './TodoItem'
6 |
7 | const Todo = ({ todos, addTodo, removeTodo }) => {
8 | const [text, changeText] = useState('')
9 |
10 | const handleAddTodo = e => {
11 | e.preventDefault()
12 |
13 | addTodo(text)
14 | changeText('')
15 | }
16 |
17 | const handleTextChange = e => {
18 | changeText(e.target.value)
19 | }
20 |
21 | return (
22 |
23 |
37 |
38 |
39 | {todos.map((todo, i) => (
40 |
41 | ))}
42 |
43 |
72 |
73 | )
74 | }
75 |
76 | export default connect(
77 | ({ todos }) => ({ todos }),
78 | { addTodo, removeTodo }
79 | )(Todo)
80 |
--------------------------------------------------------------------------------