├── .husky ├── post-merge ├── pre-commit └── commit-msg ├── next.config.js ├── .vscode ├── settings.json ├── extensions.json └── typescriptreact.code-snippets ├── postcss.config.js ├── public ├── favicon │ ├── favicon.ico │ ├── large-og.jpg │ ├── apple-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── ms-icon-70x70.png │ ├── apple-icon-57x57.png │ ├── apple-icon-60x60.png │ ├── apple-icon-72x72.png │ ├── apple-icon-76x76.png │ ├── ms-icon-144x144.png │ ├── ms-icon-150x150.png │ ├── ms-icon-310x310.png │ ├── android-icon-36x36.png │ ├── android-icon-48x48.png │ ├── android-icon-72x72.png │ ├── android-icon-96x96.png │ ├── apple-icon-114x114.png │ ├── apple-icon-120x120.png │ ├── apple-icon-144x144.png │ ├── apple-icon-152x152.png │ ├── apple-icon-180x180.png │ ├── android-icon-144x144.png │ ├── android-icon-192x192.png │ ├── apple-icon-precomposed.png │ ├── browserconfig.xml │ └── manifest.json └── fonts │ └── inter-var-latin.woff2 ├── src ├── lib │ └── helper.ts ├── pages │ ├── _app.tsx │ ├── api │ │ └── hello.ts │ ├── _document.tsx │ ├── 404.tsx │ ├── render │ │ ├── ssr.tsx │ │ ├── ssg.tsx │ │ ├── csr.tsx │ │ ├── isr.tsx │ │ └── isr-20.tsx │ ├── time.tsx │ ├── index.tsx │ └── components.tsx ├── types │ └── TimeResponse.ts ├── hooks │ └── useRealTime.tsx ├── components │ ├── links │ │ ├── CustomLink.tsx │ │ ├── ButtonLink.tsx │ │ └── UnstyledLink.tsx │ ├── buttons │ │ └── Button.tsx │ ├── Nav.tsx │ ├── TimeSection.tsx │ └── Seo.tsx └── styles │ └── globals.css ├── .prettierrc.js ├── next-sitemap.js ├── next-env.d.ts ├── vercel.json ├── .eslintrc ├── commitlint.config.js ├── .prettierignore ├── .gitignore ├── tsconfig.json ├── tailwind.config.js ├── package.json ├── CHANGELOG.md └── README.md /.husky/post-merge: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | eslint: { 3 | dirs: ['src'], 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "css.validate": false, 3 | "editor.formatOnSave": true, 4 | "editor.tabSize": 2 5 | } 6 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /public/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/nextjs-csr-ssg-isr/HEAD/public/favicon/favicon.ico -------------------------------------------------------------------------------- /public/favicon/large-og.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/nextjs-csr-ssg-isr/HEAD/public/favicon/large-og.jpg -------------------------------------------------------------------------------- /public/favicon/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/nextjs-csr-ssg-isr/HEAD/public/favicon/apple-icon.png -------------------------------------------------------------------------------- /public/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/nextjs-csr-ssg-isr/HEAD/public/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/nextjs-csr-ssg-isr/HEAD/public/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/nextjs-csr-ssg-isr/HEAD/public/favicon/favicon-96x96.png -------------------------------------------------------------------------------- /public/favicon/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/nextjs-csr-ssg-isr/HEAD/public/favicon/ms-icon-70x70.png -------------------------------------------------------------------------------- /src/lib/helper.ts: -------------------------------------------------------------------------------- 1 | export function classNames(...classes: string[]): string { 2 | return classes.filter(Boolean).join(' '); 3 | } 4 | -------------------------------------------------------------------------------- /public/favicon/apple-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/nextjs-csr-ssg-isr/HEAD/public/favicon/apple-icon-57x57.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/nextjs-csr-ssg-isr/HEAD/public/favicon/apple-icon-60x60.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/nextjs-csr-ssg-isr/HEAD/public/favicon/apple-icon-72x72.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/nextjs-csr-ssg-isr/HEAD/public/favicon/apple-icon-76x76.png -------------------------------------------------------------------------------- /public/favicon/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/nextjs-csr-ssg-isr/HEAD/public/favicon/ms-icon-144x144.png -------------------------------------------------------------------------------- /public/favicon/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/nextjs-csr-ssg-isr/HEAD/public/favicon/ms-icon-150x150.png -------------------------------------------------------------------------------- /public/favicon/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/nextjs-csr-ssg-isr/HEAD/public/favicon/ms-icon-310x310.png -------------------------------------------------------------------------------- /public/fonts/inter-var-latin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/nextjs-csr-ssg-isr/HEAD/public/fonts/inter-var-latin.woff2 -------------------------------------------------------------------------------- /public/favicon/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/nextjs-csr-ssg-isr/HEAD/public/favicon/android-icon-36x36.png -------------------------------------------------------------------------------- /public/favicon/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/nextjs-csr-ssg-isr/HEAD/public/favicon/android-icon-48x48.png -------------------------------------------------------------------------------- /public/favicon/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/nextjs-csr-ssg-isr/HEAD/public/favicon/android-icon-72x72.png -------------------------------------------------------------------------------- /public/favicon/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/nextjs-csr-ssg-isr/HEAD/public/favicon/android-icon-96x96.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/nextjs-csr-ssg-isr/HEAD/public/favicon/apple-icon-114x114.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/nextjs-csr-ssg-isr/HEAD/public/favicon/apple-icon-120x120.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/nextjs-csr-ssg-isr/HEAD/public/favicon/apple-icon-144x144.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/nextjs-csr-ssg-isr/HEAD/public/favicon/apple-icon-152x152.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/nextjs-csr-ssg-isr/HEAD/public/favicon/apple-icon-180x180.png -------------------------------------------------------------------------------- /public/favicon/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/nextjs-csr-ssg-isr/HEAD/public/favicon/android-icon-144x144.png -------------------------------------------------------------------------------- /public/favicon/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/nextjs-csr-ssg-isr/HEAD/public/favicon/android-icon-192x192.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodorusclarence/nextjs-csr-ssg-isr/HEAD/public/favicon/apple-icon-precomposed.png -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'always', 3 | singleQuote: true, 4 | jsxSingleQuote: true, 5 | tabWidth: 2, 6 | semi: true, 7 | }; 8 | -------------------------------------------------------------------------------- /next-sitemap.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | siteUrl: 'https://next-render.theodorusclarence.com/', 3 | generateRobotsTxt: true, 4 | robotsTxtOptions: { 5 | policies: [{ userAgent: '*', allow: '/' }], 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { AppProps } from 'next/app'; 2 | import '@/styles/globals.css'; 3 | 4 | function MyApp({ Component, pageProps }: AppProps) { 5 | return ; 6 | } 7 | 8 | export default MyApp; 9 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | // NOTE: This file should not be edited 6 | // see https://nextjs.org/docs/basic-features/typescript for more information. 7 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": [ 3 | { 4 | "source": "/fonts/inter-var-latin.woff2", 5 | "headers": [ 6 | { 7 | "key": "Cache-Control", 8 | "value": "public, max-age=31536000, immutable" 9 | } 10 | ] 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | 3 | import { NextApiRequest, NextApiResponse } from 'next'; 4 | 5 | export default function hello(req: NextApiRequest, res: NextApiResponse) { 6 | res.status(200).json({ name: 'Bambang' }); 7 | } 8 | -------------------------------------------------------------------------------- /public/favicon/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | // Sort Tailwind CSS class 4 | "heybourn.headwind", 5 | // Tailwind CSS Intellisense 6 | "bradlc.vscode-tailwindcss", 7 | "esbenp.prettier-vscode", 8 | "dbaeumer.vscode-eslint", 9 | "aaron-bond.better-comments" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/types/TimeResponse.ts: -------------------------------------------------------------------------------- 1 | export interface TimeResponse { 2 | abbreviation: string; 3 | client_ip: string; 4 | datetime: string; 5 | day_of_week: number; 6 | day_of_year: number; 7 | dst: boolean; 8 | dst_from: null; 9 | dst_offset: number; 10 | dst_until: null; 11 | raw_offset: number; 12 | timezone: string; 13 | unixtime: number; 14 | utc_datetime: string; 15 | utc_offset: string; 16 | week_number: number; 17 | } 18 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "node": true 6 | }, 7 | "plugins": ["@typescript-eslint"], 8 | "extends": [ 9 | "eslint:recommended", 10 | "next", 11 | "next/core-web-vitals", 12 | "plugin:@typescript-eslint/recommended" 13 | ], 14 | "rules": { 15 | "no-unused-vars": "warn", 16 | "no-console": "warn", 17 | "@typescript-eslint/explicit-module-boundary-types": "off" 18 | }, 19 | "globals": { 20 | "React": true, 21 | "JSX": true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/hooks/useRealTime.tsx: -------------------------------------------------------------------------------- 1 | import { format } from 'date-fns'; 2 | import * as React from 'react'; 3 | 4 | export default function useRealTime() { 5 | const [realTime, setRealTime] = React.useState( 6 | format(new Date(), 'kk:mm:ss O') 7 | ); 8 | 9 | React.useEffect(() => { 10 | const intervalId = setInterval(() => { 11 | setRealTime(format(new Date(), 'kk:mm:ss O')); 12 | }, 1000); 13 | 14 | return () => { 15 | clearInterval(intervalId); 16 | }; 17 | }, []); 18 | 19 | return realTime; 20 | } 21 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 4 | // TODO Add Scope Enum Here 5 | // 'scope-enum': [2, 'always', ['yourscope', 'yourscope']], 6 | 'type-enum': [ 7 | 2, 8 | 'always', 9 | [ 10 | 'feat', 11 | 'fix', 12 | 'docs', 13 | 'chore', 14 | 'style', 15 | 'refactor', 16 | 'ci', 17 | 'test', 18 | 'perf', 19 | 'revert', 20 | 'vercel', 21 | ], 22 | ], 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 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 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | # vercel 35 | .vercel 36 | -------------------------------------------------------------------------------- /.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 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | # next-sitemap 37 | robots.txt 38 | sitemap.xml -------------------------------------------------------------------------------- /src/components/links/CustomLink.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import UnstyledLink, { UnstyledLinkProps } from './UnstyledLink'; 3 | 4 | export default function CustomLink({ 5 | children, 6 | className = '', 7 | ...rest 8 | }: UnstyledLinkProps) { 9 | return ( 10 | 18 | {children} 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "baseUrl": "./src", 17 | "paths": { 18 | "@/*": ["*"] 19 | } 20 | }, 21 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 22 | "exclude": ["node_modules"], 23 | "moduleResolution": ["node_modules", ".next", "node"] 24 | } 25 | -------------------------------------------------------------------------------- /src/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import Document, { 2 | Html, 3 | Head, 4 | Main, 5 | NextScript, 6 | DocumentContext, 7 | } from 'next/document'; 8 | 9 | class MyDocument extends Document { 10 | static async getInitialProps(ctx: DocumentContext) { 11 | const initialProps = await Document.getInitialProps(ctx); 12 | return { ...initialProps }; 13 | } 14 | 15 | render() { 16 | return ( 17 | 18 | 19 | 26 | 27 | 28 |
29 | 30 | 31 | 32 | ); 33 | } 34 | } 35 | 36 | export default MyDocument; 37 | -------------------------------------------------------------------------------- /src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { RiAlarmWarningFill } from 'react-icons/ri'; 3 | 4 | import Seo from '@/components/Seo'; 5 | import CustomLink from '@/components/links/CustomLink'; 6 | 7 | export default function NotFoundPage() { 8 | return ( 9 | <> 10 | 11 | 12 |
13 |
14 |
15 | 19 |

Page Not Found

20 | 21 | Back to Home 22 | 23 |
24 |
25 |
26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/components/buttons/Button.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | 3 | type ButtonProps = { 4 | children: React.ReactNode; 5 | className?: string; 6 | variants?: 'primary' | 'secondary'; 7 | } & React.ComponentPropsWithoutRef<'button'>; 8 | 9 | export default function Button({ 10 | children, 11 | className = '', 12 | variants = 'primary', 13 | ...rest 14 | }: ButtonProps) { 15 | return ( 16 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /src/pages/render/ssr.tsx: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import * as React from 'react'; 3 | import { GetServerSideProps } from 'next'; 4 | 5 | import Seo from '@/components/Seo'; 6 | import TimeSection from '@/components/TimeSection'; 7 | 8 | import { TimeResponse } from '@/types/TimeResponse'; 9 | 10 | type SSRPageProps = { 11 | dateTime: string; 12 | }; 13 | 14 | export default function SSRPage({ dateTime }: SSRPageProps) { 15 | return ( 16 | <> 17 | 18 | 19 |
20 | 25 |
26 | 27 | ); 28 | } 29 | 30 | export const getServerSideProps: GetServerSideProps = async () => { 31 | const res = await axios.get('https://worldtimeapi.org/api/ip'); 32 | 33 | return { 34 | props: { dateTime: res.data.datetime }, 35 | }; 36 | }; 37 | -------------------------------------------------------------------------------- /src/pages/render/ssg.tsx: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import * as React from 'react'; 3 | import { GetStaticProps } from 'next'; 4 | 5 | import Seo from '@/components/Seo'; 6 | import TimeSection from '@/components/TimeSection'; 7 | 8 | import { TimeResponse } from '@/types/TimeResponse'; 9 | 10 | type SSGPageProps = { 11 | dateTime: string; 12 | }; 13 | 14 | export default function SSGPage({ dateTime }: SSGPageProps) { 15 | return ( 16 | <> 17 | 18 | 19 |
20 | 25 |
26 | 27 | ); 28 | } 29 | 30 | export const getStaticProps: GetStaticProps = async () => { 31 | const res = await axios.get('https://worldtimeapi.org/api/ip'); 32 | 33 | return { 34 | props: { dateTime: res.data.datetime }, 35 | }; 36 | }; 37 | -------------------------------------------------------------------------------- /src/components/links/ButtonLink.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import UnstyledLink, { UnstyledLinkProps } from './UnstyledLink'; 3 | 4 | type ButtonLinkProps = { 5 | variants?: 'primary' | 'secondary'; 6 | } & UnstyledLinkProps; 7 | 8 | export default function ButtonLink({ 9 | children, 10 | className = '', 11 | variants = 'primary', 12 | ...rest 13 | }: ButtonLinkProps) { 14 | return ( 15 | 29 | {children} 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /src/pages/time.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import axios from 'axios'; 3 | import Seo from '@/components/Seo'; 4 | import TimeSection from '@/components/TimeSection'; 5 | 6 | import { TimeResponse } from '@/types/TimeResponse'; 7 | 8 | export default function TimePage() { 9 | const [dateTime, setDateTime] = React.useState(); 10 | 11 | React.useEffect(() => { 12 | axios 13 | .get('https://worldtimeapi.org/api/ip') 14 | .then((res) => { 15 | setDateTime(res.data.datetime); 16 | return res.data.datetime; 17 | }) 18 | // eslint-disable-next-line no-console 19 | .catch((error) => console.error(error)); 20 | }, []); 21 | 22 | return ( 23 | <> 24 | 25 | 26 |
27 | 32 |
33 | 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /public/favicon/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "App", 3 | "icons": [ 4 | { 5 | "src": "/android-icon-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "/android-icon-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image/png", 14 | "density": "1.0" 15 | }, 16 | { 17 | "src": "/android-icon-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image/png", 20 | "density": "1.5" 21 | }, 22 | { 23 | "src": "/android-icon-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image/png", 26 | "density": "2.0" 27 | }, 28 | { 29 | "src": "/android-icon-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image/png", 32 | "density": "3.0" 33 | }, 34 | { 35 | "src": "/android-icon-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image/png", 38 | "density": "4.0" 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /src/components/Nav.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import UnstyledLink from './links/UnstyledLink'; 3 | 4 | const links = [ 5 | { href: '/', label: 'Route' }, 6 | { href: '/', label: 'Route' }, 7 | ]; 8 | 9 | export default function Nav() { 10 | return ( 11 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /src/pages/render/csr.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import axios from 'axios'; 3 | 4 | import Seo from '@/components/Seo'; 5 | import TimeSection from '@/components/TimeSection'; 6 | 7 | import { TimeResponse } from '@/types/TimeResponse'; 8 | 9 | export default function CSRPage() { 10 | const [dateTime, setDateTime] = React.useState(); 11 | 12 | React.useEffect(() => { 13 | axios 14 | .get('https://worldtimeapi.org/api/ip') 15 | .then((res) => { 16 | setDateTime(res.data.datetime); 17 | return res.data.datetime; 18 | }) 19 | // eslint-disable-next-line no-console 20 | .catch((error) => console.error(error)); 21 | }, []); 22 | 23 | return ( 24 | <> 25 | 26 | 27 |
28 | 33 |
34 | 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /src/pages/render/isr.tsx: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import * as React from 'react'; 3 | import { GetStaticProps } from 'next'; 4 | 5 | import Seo from '@/components/Seo'; 6 | import TimeSection from '@/components/TimeSection'; 7 | 8 | import { TimeResponse } from '@/types/TimeResponse'; 9 | 10 | type ISRPageProps = { 11 | dateTime: string; 12 | }; 13 | 14 | export default function ISRPage({ dateTime }: ISRPageProps) { 15 | return ( 16 | <> 17 | 18 | 19 |
20 | 25 |
26 | 27 | ); 28 | } 29 | 30 | export const getStaticProps: GetStaticProps = async () => { 31 | const res = await axios.get('https://worldtimeapi.org/api/ip'); 32 | 33 | return { 34 | props: { dateTime: res.data.datetime }, 35 | revalidate: 5, 36 | }; 37 | }; 38 | -------------------------------------------------------------------------------- /src/pages/render/isr-20.tsx: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import * as React from 'react'; 3 | import { GetStaticProps } from 'next'; 4 | 5 | import Seo from '@/components/Seo'; 6 | import TimeSection from '@/components/TimeSection'; 7 | 8 | import { TimeResponse } from '@/types/TimeResponse'; 9 | 10 | type ISR20PageProps = { 11 | dateTime: string; 12 | }; 13 | 14 | export default function ISR20Page({ dateTime }: ISR20PageProps) { 15 | return ( 16 | <> 17 | 18 | 19 |
20 | 25 |
26 | 27 | ); 28 | } 29 | 30 | export const getStaticProps: GetStaticProps = async () => { 31 | const res = await axios.get('https://worldtimeapi.org/api/ip'); 32 | 33 | return { 34 | props: { dateTime: res.data.datetime }, 35 | revalidate: 20, 36 | }; 37 | }; 38 | -------------------------------------------------------------------------------- /src/components/links/UnstyledLink.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import Link, { LinkProps } from 'next/link'; 3 | 4 | export type UnstyledLinkProps = { 5 | href: string; 6 | children: React.ReactNode; 7 | openNewTab?: boolean; 8 | className?: string; 9 | } & React.ComponentPropsWithoutRef<'a'> & 10 | LinkProps; 11 | 12 | export default function UnstyledLink({ 13 | children, 14 | href, 15 | openNewTab, 16 | className, 17 | ...rest 18 | }: UnstyledLinkProps) { 19 | const isNewTab = 20 | openNewTab !== undefined 21 | ? openNewTab 22 | : href && !href.startsWith('/') && !href.startsWith('#'); 23 | 24 | if (!isNewTab) { 25 | return ( 26 | 27 | 28 | {children} 29 | 30 | 31 | ); 32 | } 33 | 34 | return ( 35 | 42 | {children} 43 | 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const { fontFamily } = require('tailwindcss/defaultTheme'); 3 | 4 | /** @type {import("@types/tailwindcss/tailwind-config").TailwindConfig } */ 5 | module.exports = { 6 | mode: 'jit', 7 | purge: ['./src/**/*.{js,jsx,ts,tsx}'], 8 | darkMode: false, // or 'media' or 'class' 9 | theme: { 10 | extend: { 11 | fontFamily: { 12 | primary: ['Inter', ...fontFamily.sans], 13 | }, 14 | colors: { 15 | primary: { 16 | 400: '#00E0F3', 17 | 500: '#00c4fd', 18 | }, 19 | dark: '#222222', 20 | }, 21 | keyframes: { 22 | flicker: { 23 | '0%, 19.999%, 22%, 62.999%, 64%, 64.999%, 70%, 100%': { 24 | opacity: 0.99, 25 | filter: 26 | 'drop-shadow(0 0 1px rgba(252, 211, 77)) drop-shadow(0 0 15px rgba(245, 158, 11)) drop-shadow(0 0 1px rgba(252, 211, 77))', 27 | }, 28 | '20%, 21.999%, 63%, 63.999%, 65%, 69.999%': { 29 | opacity: 0.4, 30 | filter: 'none', 31 | }, 32 | }, 33 | }, 34 | animation: { 35 | flicker: 'flicker 3s linear infinite', 36 | }, 37 | }, 38 | }, 39 | variants: { 40 | extend: {}, 41 | }, 42 | plugins: [require('@tailwindcss/forms')], 43 | }; 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-csr-ssg-isr", 3 | "version": "0.1.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "prepare": "husky install", 10 | "lint": "next lint", 11 | "release": "standard-version", 12 | "format": "prettier --write .", 13 | "postbuild": "next-sitemap" 14 | }, 15 | "dependencies": { 16 | "@tailwindcss/forms": "^0.3.3", 17 | "autoprefixer": "^10.3.3", 18 | "axios": "^0.21.1", 19 | "clsx": "^1.1.1", 20 | "date-fns": "^2.23.0", 21 | "next": "^11.1.0", 22 | "postcss": "^8.3.6", 23 | "react": "^17.0.2", 24 | "react-dom": "^17.0.2", 25 | "react-icons": "^4.2.0" 26 | }, 27 | "devDependencies": { 28 | "@commitlint/cli": "^13.1.0", 29 | "@commitlint/config-conventional": "^13.1.0", 30 | "@types/react": "^17.0.19", 31 | "@types/tailwindcss": "^2.2.1", 32 | "@typescript-eslint/eslint-plugin": "^4.29.3", 33 | "@typescript-eslint/parser": "^4.29.3", 34 | "eslint": "^7.32.0", 35 | "eslint-config-next": "^11.1.0", 36 | "husky": "^7.0.2", 37 | "lint-staged": "^11.1.2", 38 | "next-sitemap": "^1.6.164", 39 | "prettier": "^2.3.0", 40 | "standard-version": "^9.3.1", 41 | "tailwindcss": "^2.2.8", 42 | "typescript": "^4.4.2" 43 | }, 44 | "lint-staged": { 45 | "src/**/*.{js,jsx,ts,tsx,json,css,scss,md,mdx}": [ 46 | "yarn eslint src --max-warnings=0", 47 | "yarn prettier --write" 48 | ] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | /* inter var - latin */ 7 | @font-face { 8 | font-family: 'Inter'; 9 | font-style: normal; 10 | font-weight: 100 900; 11 | font-display: optional; 12 | src: url('/fonts/inter-var-latin.woff2') format('woff2'); 13 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, 14 | U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, 15 | U+2215, U+FEFF, U+FFFD; 16 | } 17 | 18 | /* Write your own custom base styles here */ 19 | h1 { 20 | @apply text-3xl font-bold md:text-5xl font-primary; 21 | } 22 | 23 | h2 { 24 | @apply text-2xl font-bold md:text-4xl font-primary; 25 | } 26 | 27 | h3 { 28 | @apply text-xl font-bold md:text-3xl font-primary; 29 | } 30 | 31 | h4 { 32 | @apply text-lg font-bold font-primary; 33 | } 34 | 35 | body { 36 | @apply text-sm font-primary md:text-base; 37 | } 38 | 39 | .layout { 40 | /* 750px */ 41 | /* max-width: 43.75rem; */ 42 | 43 | /* 1100px */ 44 | max-width: 68.75rem; 45 | @apply w-11/12 mx-auto; 46 | } 47 | } 48 | 49 | @layer utilities { 50 | .animated-underline { 51 | background-image: linear-gradient(#33333300, #33333300), 52 | linear-gradient(to right, #00e0f3, #00c4fd); 53 | background-size: 100% 2px, 0 2px; 54 | background-position: 100% 100%, 0 100%; 55 | background-repeat: no-repeat; 56 | transition: background-size 0.3s ease; 57 | } 58 | .animated-underline:hover, 59 | .animated-underline:focus { 60 | background-size: 0 2px, 100% 2px; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/components/TimeSection.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { format } from 'date-fns'; 3 | 4 | import useRealTime from '@/hooks/useRealTime'; 5 | 6 | import ButtonLink from './links/ButtonLink'; 7 | import CustomLink from './links/CustomLink'; 8 | 9 | type TimeSectionProps = { 10 | dateTime: string | undefined; 11 | title: string; 12 | description: string; 13 | }; 14 | 15 | export default function TimeSection({ 16 | dateTime, 17 | title, 18 | description, 19 | }: TimeSectionProps) { 20 | const cleanDate = dateTime && format(new Date(dateTime), 'kk:mm:ss O'); 21 | 22 | const realTime = useRealTime(); 23 | 24 | return ( 25 |
26 |
27 |

{title}

28 |

{description}

29 |

30 | {cleanDate ? cleanDate : 'LOADING...'} 31 |

32 | 33 | Back to Home 34 | 35 | 36 |
37 | © {new Date().getFullYear()} By{' '} 38 | 39 | Theodorus Clarence 40 | 41 |
42 |
43 | 44 |
45 |

Real Time:

46 |

{realTime}

47 |
48 |
49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [0.1.1](https://github.com/theodorusclarence/nextjs-csr-ssg-isr/compare/v0.1.0...v0.1.1) (2021-09-01) 6 | 7 | 8 | ### Features 9 | 10 | * add footer to each page ([72d8ac9](https://github.com/theodorusclarence/nextjs-csr-ssg-isr/commit/72d8ac91f13a907fbd49c4ed95ce0e44ae0e5633)) 11 | 12 | ## 0.1.0 (2021-09-01) 13 | 14 | 15 | ### Features 16 | 17 | * add /time example page ([649555d](https://github.com/theodorusclarence/nextjs-csr-ssg-isr/commit/649555d20e23d2a35e98f870b1b6a77a3ee9e662)) 18 | * add csr, isr, ssg, ssr ([e204a16](https://github.com/theodorusclarence/nextjs-csr-ssg-isr/commit/e204a16847fd6879a8c52b90a599fed29390b32f)) 19 | * add isr-20 page ([199d6f5](https://github.com/theodorusclarence/nextjs-csr-ssg-isr/commit/199d6f530965a4fbe8d6bfae9ce66cf98fd3f995)) 20 | * add realtime to index ([62959b5](https://github.com/theodorusclarence/nextjs-csr-ssg-isr/commit/62959b535ecb20b0f100f2573d13c213d460cd89)) 21 | * add TimeSection ([39a441e](https://github.com/theodorusclarence/nextjs-csr-ssg-isr/commit/39a441e900eaf826087416d114f7d12e0b081878)) 22 | * add useRealTime ([c7c6513](https://github.com/theodorusclarence/nextjs-csr-ssg-isr/commit/c7c65132785dc55dfa65eb3fab7a4ab17ad0762c)) 23 | * change SEO ([62ae0ee](https://github.com/theodorusclarence/nextjs-csr-ssg-isr/commit/62ae0ee1a3485ec1cb84f667a86c1799ae24c16e)) 24 | * change style and copwrite isr ([7a8705f](https://github.com/theodorusclarence/nextjs-csr-ssg-isr/commit/7a8705ffa5387a72c209acab592580746b590db1)) 25 | * initial commit 🚀 ([3af7816](https://github.com/theodorusclarence/nextjs-csr-ssg-isr/commit/3af7816569cdacb69405ecb97987f2fa778440ed)) 26 | -------------------------------------------------------------------------------- /.vscode/typescriptreact.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "import React": { 3 | "prefix": "ir", 4 | "body": ["import * as React from 'react';"] 5 | }, 6 | "React.useState": { 7 | "prefix": "us", 8 | "body": [ 9 | "const [${1}, set${1/(^[a-zA-Z])(.*)/${1:/upcase}${2}/}] = React.useState(${2:initial${1/(^[a-zA-Z])(.*)/${1:/upcase}${2}/}})$0" 10 | ] 11 | }, 12 | "React.useEffect": { 13 | "prefix": "uf", 14 | "body": ["React.useEffect(() => {", " $0", "}, []);"] 15 | }, 16 | "React.useReducer": { 17 | "prefix": "ur", 18 | "body": [ 19 | "const [state, dispatch] = React.useReducer(${0:someReducer}, {", 20 | " ", 21 | "})" 22 | ] 23 | }, 24 | "React.useRef": { 25 | "prefix": "urf", 26 | "body": ["const ${1:someRef} = React.useRef($0)"] 27 | }, 28 | "React Functional Component": { 29 | "prefix": "rc", 30 | "body": [ 31 | "import * as React from 'react';\n", 32 | "export default function ${1:${TM_FILENAME_BASE}}() {", 33 | " return (", 34 | "
", 35 | " $0", 36 | "
", 37 | " )", 38 | "}" 39 | ] 40 | }, 41 | "Next Pages": { 42 | "prefix": "np", 43 | "body": [ 44 | "import * as React from 'react';\n", 45 | "import Seo from '@/components/Seo';\n", 46 | "export default function ${1:${TM_FILENAME_BASE/(^[a-zA-Z])(.*)/${1:/upcase}${2}/}}Page() {", 47 | " return (", 48 | " <>", 49 | " \n", 50 | "
\n", 51 | "
", 52 | "
", 53 | " $0", 54 | "
", 55 | "
", 56 | "
", 57 | " ", 58 | " )", 59 | "}" 60 | ] 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import useRealTime from '@/hooks/useRealTime'; 4 | 5 | import Seo from '@/components/Seo'; 6 | import CustomLink from '@/components/links/CustomLink'; 7 | 8 | export default function HomePage() { 9 | const realTime = useRealTime(); 10 | return ( 11 | <> 12 | 13 | 14 |
15 |
16 |
17 |

18 | 19 | Types of Next Rendering 20 | 21 |

22 |

23 | Demo of Next.js rendering type using time API. 24 |

25 | 26 |
27 | CSR 28 | SSR 29 | SSG 30 | ISR 31 | ISR 20s 32 |
33 | 34 |
35 | © {new Date().getFullYear()} By{' '} 36 | 37 | Theodorus Clarence 38 | 39 |
40 |
41 | 42 |
43 |

Real Time:

44 |

{realTime}

45 |
46 |
47 |
48 | 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /src/pages/components.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import Seo from '@/components/Seo'; 4 | import Button from '@/components/buttons/Button'; 5 | import CustomLink from '@/components/links/CustomLink'; 6 | import ButtonLink from '@/components/links/ButtonLink'; 7 | import UnstyledLink from '@/components/links/UnstyledLink'; 8 | 9 | export default function ComponentsPage() { 10 | return ( 11 | <> 12 | 13 | 14 |
15 |
16 |
17 |

Built-in Components

18 | 19 | ← Back to Home 20 | 21 |
    22 |
  1. 23 |

    UnstyledLink

    24 |

    25 | No style applied, differentiate internal and outside links, 26 | give custom cursor for outside links. 27 |

    28 |
    29 | Internal Links 30 | 31 | Outside Links 32 | 33 |
    34 |
  2. 35 |
  3. 36 |

    CustomLink

    37 |

    38 | Add styling on top of UnstyledLink, giving an animated 39 | underline and hover color. 40 |

    41 |
    42 | Internal Links 43 | 44 | Outside Links 45 | 46 |
    47 |
  4. 48 |
  5. 49 |

    ButtonLink

    50 |

    51 | Button styled link with 2 variants. 52 |

    53 |
    54 | Internal Links 55 | 56 | Outside Links 57 | 58 | 62 | Secondary Variant 63 | 64 |
    65 |
  6. 66 |
  7. 67 |

    Button

    68 |

    69 | Ordinary button with style. 70 |

    71 |
    72 | 75 | 81 |
    82 |
  8. 83 |
  9. 84 |

    Custom 404 Page

    85 |

    86 | Styled 404 page with some animation. 87 |

    88 |
    89 | Visit the 404 page 90 |
    91 |
  10. 92 |
93 |
94 |
95 |
96 | 97 | ); 98 | } 99 | -------------------------------------------------------------------------------- /src/components/Seo.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | import { useRouter } from 'next/router'; 3 | 4 | const defaultMeta = { 5 | title: 'Next.js Render Type', 6 | site_name: 'Next.js Render Type', 7 | description: 'A demo to differentiate CSR, SSR, SSG, and ISR', 8 | url: 'https://next-render.theodorusclarence.com', 9 | image: 'https://next-render.theodorusclarence.com/favicon/large-og.jpg', 10 | type: 'website', 11 | robots: 'follow, index', 12 | }; 13 | 14 | type SeoProps = { 15 | date?: string; 16 | templateTitle?: string; 17 | } & Partial; 18 | 19 | export default function Seo(props: SeoProps) { 20 | const router = useRouter(); 21 | const meta = { 22 | ...defaultMeta, 23 | ...props, 24 | }; 25 | meta['title'] = props.templateTitle 26 | ? `${props.templateTitle} | ${meta.site_name}` 27 | : meta.title; 28 | 29 | return ( 30 | 31 | {meta.title} 32 | 33 | 34 | 35 | 36 | {/* Open Graph */} 37 | 38 | 39 | 40 | 41 | 42 | {/* Twitter */} 43 | 44 | 45 | 46 | 47 | 48 | {meta.date && ( 49 | <> 50 | 51 | 56 | 61 | 62 | )} 63 | 64 | {/* Favicons */} 65 | {favicons.map((linkProps) => ( 66 | 67 | ))} 68 | 69 | 73 | 74 | 75 | ); 76 | } 77 | 78 | type Favicons = { 79 | rel: string; 80 | href: string; 81 | sizes?: string; 82 | type?: string; 83 | }; 84 | 85 | const favicons: Array = [ 86 | { 87 | rel: 'apple-touch-icon', 88 | sizes: '57x57', 89 | href: '/favicon/apple-icon-57x57.png', 90 | }, 91 | { 92 | rel: 'apple-touch-icon', 93 | sizes: '60x60', 94 | href: '/favicon/apple-icon-60x60.png', 95 | }, 96 | { 97 | rel: 'apple-touch-icon', 98 | sizes: '72x72', 99 | href: '/favicon/apple-icon-72x72.png', 100 | }, 101 | { 102 | rel: 'apple-touch-icon', 103 | sizes: '76x76', 104 | href: '/favicon/apple-icon-76x76.png', 105 | }, 106 | { 107 | rel: 'apple-touch-icon', 108 | sizes: '114x114', 109 | href: '/favicon/apple-icon-114x114.png', 110 | }, 111 | { 112 | rel: 'apple-touch-icon', 113 | sizes: '120x120', 114 | href: '/favicon/apple-icon-120x120.png', 115 | }, 116 | { 117 | rel: 'apple-touch-icon', 118 | sizes: '144x144', 119 | href: '/favicon/apple-icon-144x144.png', 120 | }, 121 | { 122 | rel: 'apple-touch-icon', 123 | sizes: '152x152', 124 | href: '/favicon/apple-icon-152x152.png', 125 | }, 126 | { 127 | rel: 'apple-touch-icon', 128 | sizes: '180x180', 129 | href: '/favicon/apple-icon-180x180.png', 130 | }, 131 | { 132 | rel: 'icon', 133 | type: 'image/png', 134 | sizes: '192x192', 135 | href: '/favicon/android-icon-192x192.png', 136 | }, 137 | { 138 | rel: 'icon', 139 | type: 'image/png', 140 | sizes: '32x32', 141 | href: '/favicon/favicon-32x32.png', 142 | }, 143 | { 144 | rel: 'icon', 145 | type: 'image/png', 146 | sizes: '96x96', 147 | href: '/favicon/favicon-96x96.png', 148 | }, 149 | { 150 | rel: 'icon', 151 | type: 'image/png', 152 | sizes: '16x16', 153 | href: '/favicon/favicon-16x16.png', 154 | }, 155 | { 156 | rel: 'manifest', 157 | href: '/favicon/manifest.json', 158 | }, 159 | ]; 160 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Next.js + Tailwind CSS + TypeScript Starter 2 | 3 | [![CodeFactor](https://www.codefactor.io/repository/github/theodorusclarence/ts-nextjs-tailwind-starter/badge/main)](https://www.codefactor.io/repository/github/theodorusclarence/ts-nextjs-tailwind-starter/overview/main) 4 | [![Depfu](https://badges.depfu.com/badges/fc6e730632ab9dacaf7df478a08684a7/overview.svg)](https://depfu.com/github/theodorusclarence/ts-nextjs-tailwind-starter?project_id=30160) 5 | 6 | This is a Next.js, Tailwind CSS, and Typescript project bootstrapped using [ts-nextjs-tailwind-starter](https://github.com/theodorusclarence/ts-nextjs-tailwind-starter) created by [Theodorus Clarence](https://github.com/theodorusclarence/ts-nextjs-tailwind-starter). 7 | 8 | ![ts-nextjs-tailwind-starter](https://socialify.git.ci/theodorusclarence/ts-nextjs-tailwind-starter/image?description=1&descriptionEditable=A%20starter%20for%20Next.js%2C%20Tailwind%20CSS%2C%20and%20Typescript%20with%20Absolute%20Import%2C%20Seo%2C%20Link%20component%2C%20pre-configured%20with%20Husky.&language=1&owner=1&pattern=Charlie%20Brown&stargazers=1&theme=Dark) 9 | 10 | ## Getting Started 11 | 12 | To use this template you can use one of the three ways: 13 | 14 | ### 1. Using `create-next-app` 15 | 16 | ```bash 17 | npx create-next-app -e https://github.com/theodorusclarence/ts-nextjs-tailwind-starter project-name 18 | ``` 19 | 20 | ### 2. Use this repository as template 21 | 22 | ![image](https://user-images.githubusercontent.com/55318172/129183039-1a61e68d-dd90-4548-9489-7b3ccbb35810.png) 23 | 24 | ### 3. Deploy to Vercel 25 | 26 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Ftheodorusclarence%2Fts-nextjs-tailwind-starter) 27 | 28 | Then, run the development server: 29 | 30 | ```bash 31 | npm run dev 32 | # or 33 | yarn dev 34 | ``` 35 | 36 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 37 | 38 | You can start editing the page by modifying `src/pages/index.tsx`. The page auto-updates as you edit the file. 39 | 40 | ## What's Inside 41 | 42 | ### 1. Installed Package 43 | 44 | 1. [clsx](https://bundlephobia.com/package/clsx@latest), utility for constructing `className` strings conditionally. 45 | 2. [react-icons](https://bundlephobia.com/package/react-icons@latest), svg react icons of popular icon packs. 46 | 47 | ### 2. UnstyledLink Component 48 | 49 | Used as a component for Next.js Link. Will render out Next/Link if the href started with `/` or `#`, else will render an `a` tag with `target='_blank'`. Also add a cursor style for outside links 50 | 51 | ### 3. CustomLink Component 52 | 53 | ![customlink](https://user-images.githubusercontent.com/55318172/129183546-4e8c2059-0493-4459-a1e9-755fbd32fe39.gif) 54 | 55 | ### 4. Absolute Import 56 | 57 | You can import without using relative path 58 | 59 | ```tsx 60 | import Nav from '../../../components/Nav'; 61 | 62 | simplified to 63 | 64 | import Nav from '@/components/Nav'; 65 | ``` 66 | 67 | ### 5. Seo Component 68 | 69 | Configure the default in `src/components/Seo.tsx`. If you want to use the default, just add `` on top of your page. 70 | 71 | You can also customize it per page by overriding the title, description as props 72 | 73 | ```tsx 74 | 75 | ``` 76 | 77 | or if you want to still keep the title like `%s | Next.js Tailwind Starter`, you can use `templateTitle` props. 78 | 79 | ### 6. Custom 404 Page 80 | 81 | ![404](https://user-images.githubusercontent.com/55318172/129184274-d90631f2-6688-4ed2-bef2-a4d018a4863c.gif) 82 | 83 | ### 7. Workspace Snippets 84 | 85 | Snippets such as React import, useState, useEffect, React Component. [View more](/.vscode/typescriptreact.code-snippets) 86 | 87 | ### 8. Husky, Prettier, Lint, and Commitlint Configured 88 | 89 | 3 Husky hooks including: 90 | 91 | 1. pre-commit, running `next lint` and format the code using prettier 92 | 2. commit-msg, running commitlint to ensure the use of [Conventional Commit](https://theodorusclarence.com/library/conventional-commit-readme) for commit messages 93 | 3. post-merge, running `yarn` every `git pull` or after merge to ensure all new packages are installed and ready to go 94 | 95 | ### 9. Default Favicon Declaration 96 | 97 | Use [Favicon Generator](https://www.favicon-generator.org/) and then overwrite the files in `/public/favicon` 98 | 99 | ### 10. Default Tailwind CSS Base Styles 100 | 101 | There are default styles for responsive heading sizes, and `.layout` to support a max-width for larger screen size. Find more about it on [my blog post](https://theodorusclarence.com/blog/tailwindcss-best-practice#1-using-layout-class-or-container) 102 | 103 | ### 11. Preloaded & Self Hosted Inter Fonts 104 | 105 | Inter fonts is a variable fonts that is self hosted and preloaded. 106 | --------------------------------------------------------------------------------