Skip to content

Instantly share code, notes, and snippets.

@nurmdrafi
Created January 21, 2023 08:43
Show Gist options
  • Save nurmdrafi/cd4a14282dee9d41ed10a9016fb9e670 to your computer and use it in GitHub Desktop.
Save nurmdrafi/cd4a14282dee9d41ed10a9016fb9e670 to your computer and use it in GitHub Desktop.
Next.js + Typescript Project Setup

Index

Introduction

Create Next App

npx create-next-app <project-name>

Would you like to use TypeScript with this project?  No / Yes

Would you like to use Eslint with this project?  No / Yes

Go to current directory

npm run dev

Why use Create Next App?

create-next-app allows you to create a new Next.js app within seconds. It is officially maintained by the creators of Next.js, and includes a number of benefits:

  • Interactive Experience: Running npx create-next-app@latest (with no arguments) launches an interactive experience that guides you through setting up a project.

  • Zero Dependencies: Initializing a project is as quick as one second. Create Next App has zero dependencies.

  • Offline Support: Create Next App will automatically detect if you're offline and bootstrap your project using your local package cache.

  • Support for Examples: Create Next App can bootstrap your application using an example from the Next.js examples collection (e.g. npx create-next-app --example api-routes).

  • Tested: The package is part of the Next.js monorepo and tested using the same integration test suite as Next.js itself, ensuring it works as expected with every release.

Folder Structure

my-app
├── .next
├── components
├── node_modules
├── pages
   ├── api
   ├── _app.tsx
   ├── _document.tsx
   ├── index.tsx
   ├── 404.tsx
   ├── login.tsx
   └── signup.tsx
├── public
   ├── icons
   ├── images
   ├── favicon.ico
   ├── next.svg
   ├── thirteen.svg
   └── vercel.svg
├── redux
   ├── actions
   ├── reducers
   └── store.ts
├── styles
   └── globals.css
├── utils
├── .env.example
├── .env.local
├── .eslintrc.json
├── .gitignore
├── App.config.js
├── next.config.js
├── next-env.d.ts
├── next.config.js
├── package-lock.json
├── package.json
├── README.md
└── tsconfig.json

Basic Setup

.env.local

NEXT_PUBLIC_AUTH_URL='AUTH_URL'
NEXT_PUBLIC_BASE_URL='BASE_URL'
NEXT_PUBLIC_MAP_API_ACCESS_TOKEN='MAP_API_ACCESS_TOKEN'

App.config.js

export const AUTH_URL = process.env.NEXT_PUBLIC_AUTH_URL;
export const MAP_API_ACCESS_TOKEN = process.env.NEXT_PUBLIC_MAP_API_ACCESS_TOKEN;

// Staging
export const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL;

// Auth Configs
export const AUTH = {
	LOGIN: `${AUTH_URL}/admin/login`,
	GET_USER: `${AUTH_URL}/auth/user`,
    REGISTER: `${AUTH_URL}/auth/register`,
};

// API Configs
export const API = {}

_app.tsx

import Head from "next/head";
import type { AppProps } from 'next/app'
import { useRouter } from 'next/router'
import { Provider } from 'react-redux'
import store from '../redux/store'

// Import Components
import MenuLayout from '../components/MenuLayout'

// Import Styles
import '../styles/globals.css'

const App = ({ Component, pageProps }: AppProps) => {

  // States
  const router = useRouter()

  return (
    <>
      <Provider store={ store }>
        <div style={containerStyles}>
          { (router?.asPath === '/login' || router?.asPath === '/signup' || router?.asPath === '/') ?
            (
              <Component { ...pageProps } />
            )
            :
            (
              <MenuLayout>
                <Component { ...pageProps } />
              </MenuLayout>
            )
          }

        </div>
      </Provider>
    </>
  )
}

// Styles
const containerStyles = {
  boxSizing: 'border-box' as 'border-box',
  width: '100%',
  height: '100vh',
  overflow: 'hidden'
}

export default App

_document.tsx

import type { DocumentContext, DocumentInitialProps } from 'next/document'
import Document, { Html, Head, Main, NextScript } from 'next/document'
import { createCache, extractStyle, StyleProvider } from '@ant-design/cssinjs'
import Script from 'next/script'

class MyDocument extends Document {
  static async getInitialProps(
    ctx: DocumentContext
  ): Promise<DocumentInitialProps> {
    const cache = createCache()
    const originalRenderPage = ctx.renderPage

    // Run the React rendering logic synchronously
    ctx.renderPage = () =>
      originalRenderPage({
        // Wrapping The Whole React Tree
        enhanceApp: (App) => (props) =>
          (
            <StyleProvider cache={cache}>
              <App {...props} />
            </StyleProvider>
          ),
      })

    const initialProps = await Document.getInitialProps(ctx)

    return {
      ...initialProps,
      styles: (
        <>
          {initialProps.styles}
          <script
            dangerouslySetInnerHTML={{
              __html: `</script>${extractStyle(cache)}<script>`,
            }}
          />
        </>
      ),
    }
  }
  render() {
    return (
      <Html>
        <Head>
          <link rel="stylesheet" href="https://cdn.barikoi.com/bkoi-gl-js/dist/bkoi-gl.css"/>
          <Script src="https://cdn.barikoi.com/bkoi-gl-js/dist/bkoi-gl.js" strategy="beforeInteractive" />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

export default MyDocument

index.tsx

import { useRouter } from 'next/router'
import { useEffect } from 'react'

// Import Components
import { Spin } from 'antd'

// Import Actions & Methods
import { useAppSelector, useAppDispatch } from '../redux/store'
import { validateUser } from '../redux/actions/authActions'

export default function Home() {

  // States
  const router = useRouter()
  const dispatch = useAppDispatch()

  // Get Data from Redux Store
  const isAuthenticated = useAppSelector(state => state?.auth?.isAuthenticated ?? false)
  const isValidating = useAppSelector(state => state?.auth?.isValidating ?? true)

  // On Load Validate User by Token
  useEffect(() => {
      const token = localStorage.getItem('token')
      dispatch( validateUser(token) )
  }, [])

  // On Load Redirect User
  useEffect(() => {
    if(isAuthenticated && !isValidating) {
      console.log("dashboard")
      router.push('/dashboard')
      
    } else if(!isAuthenticated && !isValidating) {
      console.log("login")
      router.push('/login')
    }
  }, [ isAuthenticated, isValidating ])    

  return (
    
    <div style={ containerStyles }>
      <Spin size='large' />
    </div>
  )
}

// Styles
const containerStyles = {
  width: '100%',
  height: '100%',
  display: 'flex',
  justifyContent: 'center',
  alignItems: 'center'
}

store.tsx

import { configureStore } from "@reduxjs/toolkit";
import type { TypedUseSelectorHook } from 'react-redux'
import { useDispatch, useSelector } from 'react-redux'

// Import Reducers
import authReducer from "./reducers/authReducer";
import navReducer from "./reducers/navReducer";

const store = configureStore({
	reducer: {
		auth: authReducer,
		nav: navReducer,
	},
});

// Declare Typed Definitions
type RootState = ReturnType<typeof store.getState>
type AppDispatch = typeof store.dispatch

export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

export default store;

MenuLayout.tsx

import React from 'react';

// Import Components
import { Layout } from 'antd';
import LeftNav from './LeftNav/LeftNav'
import TopNav from './TopNav/TopNav'

// Import Methods
import { useAppSelector } from '../redux/store';

// Import Constants
const { Header, Sider, Content } = Layout

// Interface
interface Props {
    children: React.ReactNode
}

const MenuLayout = ({ children }: Props) => {

    // Get Data from Redux
    const isLeftNavOpen = useAppSelector(state => state?.nav?.isLeftNavOpen ?? true)
    const leftNavWidth = useAppSelector(state => state?.nav?.leftNavWidth ?? 340)
    const selectedLeftNavMenuKeys = useAppSelector(state => state?.nav?.selectedLeftNavMenuKeys ?? [ 'dashboard' ])

    return (
        <div style={ containerStyles }>
            
            <Layout>
                <Header style={{width: "100%"}}>
                    <TopNav/>
                </Header>
                <Layout hasSider={true}>
                    <Sider
                        className='left-nav-container'
                        theme='light'
                        trigger={ null }
                        collapsed={ !isLeftNavOpen }
                        collapsedWidth={ 100 }
                        collapsible={ true }
                        width={ leftNavWidth }
                    >
                        <LeftNav selectedLeftNavMenuKeys={ selectedLeftNavMenuKeys }/>
                    </Sider>
                    
                    <Content>{ children }</Content>
                </Layout>
            </Layout>
        </div>
    );
};

// Styles
const containerStyles = {
    width: '100%',
    height: '100%',
    overflow: 'hidden'
  }

export default MenuLayout;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment