├── .gitignore ├── README.md ├── components ├── hoc │ └── withAuth.js ├── layout │ ├── footer.js │ ├── header.js │ ├── index.js │ └── styles │ │ └── header.scss └── shared │ ├── link.js │ ├── localeSwitcher.js │ ├── meta.js │ └── translate.js ├── env.js ├── helpers └── analytics.js ├── jsconfig.json ├── locales ├── config.js ├── index.js ├── localeContext.js ├── translation │ ├── ar.json │ ├── en.json │ └── index.js ├── useLocale.js └── withLocale.js ├── next.config.js ├── package.json ├── pages ├── [lang] │ ├── about.js │ └── index.js ├── _app.js ├── _document.js ├── _error.js └── index.js ├── public ├── bootstrap │ ├── bootstrap-rtl.min.css │ └── bootstrap.min.css ├── favicon.ico └── images │ ├── 2.jpg │ └── 2.png ├── scss ├── variables.scss └── web.scss └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | .env* 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NextJS Boilerplate 2 | 3 | React NextJS Boilerplate and Starting Template 4 | 5 | Multilanguage support using simple i18 pattern. 6 | 7 | Contains authentication Higher Order Components (hoc) 8 | to protect urls and to prevent authenticated user from visiting pages such as login, 9 | login and logout functions to store token in the cookies. 10 | 11 | Reactstrap, Google Analytics, Layout, language switcher, locale next link component, 12 | documented meta component,scss and import from root implemented. 13 | 14 | not a big project but you can try it here 15 | 16 | Demo: https://boilerplate.itmohou.now.sh 17 | 18 | just clone the repo. 19 | 20 | ``` yarn ``` 21 | or 22 | ``` npm install ``` 23 | 24 | then run it, 25 | 26 | ``` yarn dev ``` 27 | or 28 | ``` npm run dev ``` 29 | 30 | 31 | please don't hesitate to suggest more features into the boilerplate 32 | 33 | mail: it.moh.ou@gmail.com for any help. 34 | 35 | ![NextJS Boilerplate](https://i.ibb.co/d6mjfv7/result.png) 36 | -------------------------------------------------------------------------------- /components/hoc/withAuth.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import Router from 'next/router' 3 | import nextCookie from 'next-cookies' 4 | import cookie from 'js-cookies' 5 | import { TOKEN_COOKIES_AGE } from '~/env' 6 | 7 | 8 | // * This function recieves a token to save it to token, 9 | export const login = async ({ token }) => { 10 | cookie.setItem('token', token, { expires: TOKEN_COOKIES_AGE }) 11 | Router.push('/') 12 | } 13 | 14 | 15 | // * This function will clear cookies from token 16 | export const logout = () => { 17 | cookie.removeItem('token') 18 | Router.push('/login') 19 | } 20 | 21 | 22 | // * This HOC should be used on pages such as login, register, reset password. 23 | // * To prevent the user from being able to visit them when he is logged in 24 | export const redirectOnAuth = WrappedComponent => { 25 | const Wrapper = (props) => { 26 | return 27 | } 28 | 29 | Wrapper.getInitialProps = async (ctx) => { 30 | const { token } = nextCookie(ctx); 31 | if (ctx.req && token) { 32 | ctx.res.writeHead(302, { Location: '/' }) 33 | ctx.res.end() 34 | return 35 | } 36 | if (token) { 37 | Router.push('/') 38 | } 39 | 40 | let componentProps = {}; 41 | if (WrappedComponent.getInitialProps) { 42 | componentProps = await WrappedComponent.getInitialProps(ctx); 43 | } 44 | return { ...componentProps,token } 45 | 46 | } 47 | return Wrapper; 48 | } 49 | 50 | 51 | // * Pages that need authentication token should be wrapped with this HOC to redirect them to login, 52 | // * You can modify it as you like for other behaviors such as storing the requested url before redirecting to login 53 | // * in case you wanted to redirect back to the requested url if he successfully authenticate. 54 | export const withAuth = WrappedComponent => { 55 | const Wrapper = (props) => { 56 | const syncLogout = (event) => { 57 | if (event.key === 'logout') { 58 | Router.push('/login') 59 | } 60 | } 61 | useEffect(() => { 62 | window.addEventListener('storage', syncLogout) 63 | 64 | return () => { 65 | window.removeEventListener('storage', syncLogout) 66 | window.localStorage.removeItem('logout') 67 | } 68 | }, []) 69 | return 70 | 71 | } 72 | 73 | Wrapper.getInitialProps = async (ctx) => { 74 | const token = auth(ctx) 75 | 76 | let componentProps = {}; 77 | if (WrappedComponent.getInitialProps) { 78 | componentProps = await WrappedComponent.getInitialProps(ctx); 79 | } 80 | return { ...componentProps, token } 81 | } 82 | return Wrapper; 83 | 84 | } 85 | 86 | 87 | export const auth = ctx => { 88 | const { token } = nextCookie(ctx) 89 | 90 | /* 91 | * This happens on server only, ctx.req is available means it's being 92 | * rendered on server. If we are on server and token is not available, 93 | * means user is not logged in. 94 | */ 95 | if (ctx.req && !token) { 96 | ctx.res.writeHead(302, { Location: '/login' }) 97 | ctx.res.end() 98 | return 99 | } 100 | 101 | // We already checked for server. This should only happen on client. 102 | if (!token) { 103 | Router.push('/login') 104 | } 105 | 106 | return token 107 | } -------------------------------------------------------------------------------- /components/layout/footer.js: -------------------------------------------------------------------------------- 1 | 2 | const Footer = () => { 3 | 4 | return (
5 | 6 |
) 7 | } 8 | 9 | export default Footer; -------------------------------------------------------------------------------- /components/layout/header.js: -------------------------------------------------------------------------------- 1 | import { Fragment, useState } from "react"; 2 | import NextLink from "../shared/link"; 3 | import Translate from "../shared/translate"; 4 | import { useLocale } from "~/locales"; 5 | import LocaleSwitcher from '../shared/localeSwitcher'; 6 | import '~/components/layout/styles/header.scss' 7 | import { 8 | Collapse, 9 | Navbar, 10 | NavbarToggler, 11 | Nav, 12 | NavItem, 13 | } from 'reactstrap'; 14 | 15 | const links = [ 16 | { href: '/', text: , }, 17 | { href: '/about', text: , }, 18 | { href: '/contact', text: , }, 19 | ]; 20 | 21 | const Header = () => { 22 | const [isOpen, setIsOpen] = useState(false); 23 | 24 | const toggle = () => setIsOpen(!isOpen); 25 | return ( 26 |
27 | {/* */} 30 | 31 | reactstrap 32 | 33 | 34 | 41 | 42 | 43 | 44 |
45 | ) 46 | } 47 | 48 | export default Header; -------------------------------------------------------------------------------- /components/layout/index.js: -------------------------------------------------------------------------------- 1 | import Header from "./header"; 2 | import Footer from "./footer"; 3 | 4 | const Layout = ({ children }) => { 5 | return ( 6 |
7 |
8 | {children} 9 |
10 |