├── .eslintrc.json ├── .gitignore ├── .npmignore ├── .vscode ├── extensions.json └── settings.json ├── README.md ├── package.json ├── src ├── cli │ ├── index.ts │ └── validation.ts ├── constants.ts ├── index.ts ├── templates │ ├── base │ │ ├── .env.local.example │ │ ├── .eslintrc.json │ │ ├── .gitignore │ │ ├── .tool-versions │ │ ├── .vscode │ │ │ ├── extensions.json │ │ │ └── settings.json │ │ ├── README.md │ │ ├── app │ │ │ ├── constants.ts │ │ │ ├── error.tsx │ │ │ ├── head.tsx │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── components │ │ │ ├── common │ │ │ │ ├── Carousel │ │ │ │ │ ├── Carousel.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── Chip │ │ │ │ │ ├── Chip.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── ContentPageLoadingState │ │ │ │ │ ├── ContentPageLoadingState.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── Footer │ │ │ │ │ ├── Footer.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── Navbar │ │ │ │ │ ├── NavItem │ │ │ │ │ │ ├── NavItem.tsx │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── Navbar.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── NotionRenderer │ │ │ │ │ ├── NotionRenderer.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── PageTitle │ │ │ │ │ ├── PageTitle.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── SearchList │ │ │ │ │ ├── SearchList.tsx │ │ │ │ │ └── index.ts │ │ │ │ └── Skeleton │ │ │ │ │ ├── Skeleton.tsx │ │ │ │ │ └── index.tsx │ │ │ └── home │ │ │ │ └── BlogHighlightCard │ │ │ │ ├── BlogHighlightCard.tsx │ │ │ │ └── index.ts │ │ ├── icons │ │ │ ├── arrow-right.tsx │ │ │ ├── article.tsx │ │ │ ├── book.tsx │ │ │ ├── calendar.tsx │ │ │ ├── channel.tsx │ │ │ ├── cross.tsx │ │ │ ├── envelope.tsx │ │ │ ├── hamburger.tsx │ │ │ ├── link.tsx │ │ │ ├── search.tsx │ │ │ ├── technologies │ │ │ │ ├── database │ │ │ │ │ ├── dynamo.tsx │ │ │ │ │ ├── mongo.tsx │ │ │ │ │ └── postgres.tsx │ │ │ │ ├── development │ │ │ │ │ ├── apple.tsx │ │ │ │ │ └── vscode.tsx │ │ │ │ ├── infrastructure │ │ │ │ │ ├── aws.tsx │ │ │ │ │ ├── cognito.tsx │ │ │ │ │ ├── docker.tsx │ │ │ │ │ ├── ec2.tsx │ │ │ │ │ ├── git.tsx │ │ │ │ │ ├── github.tsx │ │ │ │ │ ├── lambda.tsx │ │ │ │ │ ├── s3.tsx │ │ │ │ │ └── terraform.tsx │ │ │ │ ├── languages │ │ │ │ │ ├── java.tsx │ │ │ │ │ ├── python.tsx │ │ │ │ │ └── typescript.tsx │ │ │ │ ├── mobile │ │ │ │ │ ├── android.tsx │ │ │ │ │ ├── capacitor.tsx │ │ │ │ │ ├── expo.tsx │ │ │ │ │ ├── ionic.tsx │ │ │ │ │ └── react-native.tsx │ │ │ │ ├── web-backend │ │ │ │ │ ├── django.tsx │ │ │ │ │ ├── firebase.tsx │ │ │ │ │ ├── nest.tsx │ │ │ │ │ └── serverless.tsx │ │ │ │ └── web-frontend │ │ │ │ │ ├── gatsby.tsx │ │ │ │ │ ├── next-js.tsx │ │ │ │ │ └── sveltekit.tsx │ │ │ └── video.tsx │ │ ├── next.config.js │ │ ├── package.json │ │ ├── pages │ │ │ ├── 404.tsx │ │ │ ├── _app.tsx │ │ │ └── _document.tsx │ │ ├── postcss.config.js │ │ ├── public │ │ │ ├── about.jpg │ │ │ └── avatar.jpg │ │ ├── server │ │ │ └── services │ │ │ │ └── cms │ │ │ │ ├── cms.client.ts │ │ │ │ ├── cms.types.ts │ │ │ │ └── cms.utils.ts │ │ ├── styles │ │ │ ├── ThemeProvider.tsx │ │ │ └── globals.css │ │ ├── tailwind.config.js │ │ ├── tsconfig.json │ │ ├── types │ │ │ ├── cms.ts │ │ │ ├── guards.ts │ │ │ └── nextjs.ts │ │ └── yarn.lock │ └── extra-pages │ │ ├── about │ │ └── app │ │ │ └── page.tsx │ │ ├── blog │ │ ├── app │ │ │ ├── [...slug] │ │ │ │ ├── loading.tsx │ │ │ │ ├── not-found.tsx │ │ │ │ └── page.tsx │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ └── components │ │ │ ├── BlogHeader │ │ │ ├── BlogHeader.tsx │ │ │ └── index.ts │ │ │ ├── BlogLinkCard │ │ │ ├── BlogLinkCard.tsx │ │ │ └── index.ts │ │ │ ├── BlogList │ │ │ ├── BlogList.tsx │ │ │ └── index.ts │ │ │ └── BlogLoadingState │ │ │ ├── BlogLoadingState.tsx │ │ │ └── index.ts │ │ ├── journal │ │ ├── app │ │ │ ├── [...slug] │ │ │ │ ├── loading.tsx │ │ │ │ ├── not-found.tsx │ │ │ │ └── page.tsx │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ └── components │ │ │ ├── JournalEntryList │ │ │ ├── JournalEntryList.tsx │ │ │ └── index.ts │ │ │ ├── JournalEntryRow │ │ │ ├── JournalEntryRow.tsx │ │ │ └── index.ts │ │ │ ├── JournalEntryRowSkeleton │ │ │ ├── JournalEntryRowSkeleton.tsx │ │ │ └── index.ts │ │ │ ├── JournalHeader │ │ │ ├── JournalHeader.tsx │ │ │ └── index.ts │ │ │ └── JournalLoadingState │ │ │ ├── JournalLoadingState.tsx │ │ │ └── index.ts │ │ ├── resources │ │ ├── app │ │ │ ├── constants.ts │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ └── components │ │ │ ├── ResourceList │ │ │ ├── ResourceList.tsx │ │ │ └── index.ts │ │ │ ├── ResourcesHeader │ │ │ ├── ResourcesHeader.tsx │ │ │ └── index.ts │ │ │ ├── ResourcesLinkCard │ │ │ ├── ResourcesLinkCard.tsx │ │ │ └── index.ts │ │ │ └── ResourcesLinkCardSkeleton │ │ │ ├── ResourcesLinkCardSkeleton.tsx │ │ │ └── index.ts │ │ └── tech │ │ ├── app │ │ ├── constants.ts │ │ └── page.tsx │ │ └── components │ │ └── TechListDisplay │ │ ├── TechListDisplay.tsx │ │ └── index.ts ├── types.ts └── utils.ts ├── tsconfig.json └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": [ 4 | "eslint:recommended", 5 | "react-app", 6 | "plugin:prettier/recommended" 7 | ], 8 | "rules": { 9 | "curly": ["error", "all"], 10 | "eqeqeq": ["error", "smart"], 11 | "import/no-extraneous-dependencies": [ 12 | "error", 13 | { 14 | "devDependencies": true, 15 | "optionalDependencies": false, 16 | "peerDependencies": false 17 | } 18 | ], 19 | "no-shadow": "error", 20 | "@typescript-eslint/no-shadow": "error", 21 | "prefer-const": "error", 22 | "import/order": [ 23 | "error", 24 | { 25 | "groups": [ 26 | ["external", "builtin"], 27 | "internal", 28 | ["parent", "sibling", "index"] 29 | ] 30 | } 31 | ], 32 | "sort-imports": [ 33 | "error", 34 | { 35 | "ignoreCase": true, 36 | "ignoreDeclarationSort": true, 37 | "ignoreMemberSort": false, 38 | "memberSyntaxSortOrder": ["none", "all", "multiple", "single"] 39 | } 40 | ], 41 | "padding-line-between-statements": [ 42 | "error", 43 | { 44 | "blankLine": "always", 45 | "prev": "*", 46 | "next": "return" 47 | } 48 | ], 49 | "react/no-string-refs": "warn", 50 | "react-hooks/rules-of-hooks": "error", 51 | "react-hooks/exhaustive-deps": "warn", 52 | "@typescript-eslint/explicit-function-return-type": "off" 53 | }, 54 | "plugins": ["import"], 55 | "env": { 56 | "browser": true, 57 | "es6": true, 58 | "node": true, 59 | "jest": true 60 | }, 61 | "settings": { 62 | "react": { 63 | "version": "detect" 64 | } 65 | }, 66 | "overrides": [ 67 | { 68 | "files": ["**/*.ts?(x)"], 69 | "extends": [ 70 | "plugin:@typescript-eslint/recommended", 71 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 72 | "plugin:prettier/recommended" 73 | ], 74 | "parser": "@typescript-eslint/parser", 75 | "parserOptions": { 76 | "project": "tsconfig.json" 77 | }, 78 | "rules": { 79 | "@typescript-eslint/prefer-optional-chain": "error", 80 | "no-shadow": "off", 81 | "@typescript-eslint/no-shadow": "error", 82 | "@typescript-eslint/prefer-nullish-coalescing": "error", 83 | "@typescript-eslint/strict-boolean-expressions": [ 84 | "error", 85 | { 86 | "allowString": false, 87 | "allowNumber": false, 88 | "allowNullableObject": true 89 | } 90 | ], 91 | "@typescript-eslint/no-unnecessary-boolean-literal-compare": "error", 92 | "@typescript-eslint/no-unnecessary-condition": "error", 93 | "@typescript-eslint/no-unnecessary-type-arguments": "error", 94 | "@typescript-eslint/prefer-string-starts-ends-with": "error", 95 | "@typescript-eslint/switch-exhaustiveness-check": "error", 96 | "@typescript-eslint/restrict-template-expressions": [ 97 | "error", 98 | { 99 | "allowNumber": true, 100 | "allowBoolean": true 101 | } 102 | ] 103 | } 104 | }, 105 | ] 106 | } 107 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "streetsidesoftware.code-spell-checker", 4 | "dbaeumer.vscode-eslint", 5 | "esbenp.prettier-vscode", 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "editor.formatOnSave": false, // This makes sure we don't have conflicts between eslint and prettier. 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll.eslint": true, 6 | "source.fixAll.markdownlint": true 7 | }, 8 | "[typescript]": { 9 | "editor.defaultFormatter": "esbenp.prettier-vscode" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 |
7 | 8 |

📚 Notion Blog Builder CLI

9 | 10 | [![NPM version](https://img.shields.io/npm/v/next-notion-blog-builder.svg?style=flat)](https://www.npmjs.com/package/next-notion-blog-builder) [![NPM monthly downloads](https://img.shields.io/npm/dm/next-notion-blog-builder.svg?style=flat)](https://npmjs.org/package/next-notion-blog-builder) [![NPM total downloads](https://img.shields.io/npm/dt/next-notion-blog-builder.svg?style=flat)](https://npmjs.org/package/next-notion-blog-builder) 11 | 12 | Want to start posting blog content and stand out from [Medium](https://www.medium.com) and [DEV](https://dev.to/t/blog)? Use this CLI to quickly generate yourself a NextJS blog which uses Notion as a CMS to easily store and edit all your articles! 13 | 14 | ### [Example Website: check out my site to see what you can do!](https://www.jameshw.dev) 15 | ### [Example Notion CMS](https://jdhw.notion.site/jdhw/Next-Notion-Blog-Template-8e961bdf11d64f8cb20787c53f43b422) 16 | ### [View on NPMJS](https://www.npmjs.com/package/next-notion-blog-builder) 17 | 18 | 19 |
20 | 21 | ## 🌱 Getting started 22 | 23 | ### ⚙️ Generate the project with the CLI 24 | 25 | 1. Run: `npx next-notion-blog-builder` and follow the CLI. 26 | 2. `cd` into your `` directory 27 | 3. Run: `yarn dev` to start the development server on [http://localhost:3000](http://localhost:3000) 28 | 4. Update the `page.tsx` files in the `/app` directory to fill in the blanks! 29 | 30 | ### 💿 Create Notion databases for the CMS 31 | 32 | See the [Notion template page](https://jdhw.notion.site/jdhw/Next-Notion-Blog-Template-8e961bdf11d64f8cb20787c53f43b422) for the database you will need: 33 | 34 | 1. Copy this page into your personal Notion space. 35 | 2. Follow the steps in the [NotionAPI Docs](https://developers.notion.com/docs/create-a-notion-integration) to create an integration: 36 | - give the integration read-only permissions; 37 | - share each database you need with that integration (`Add connections`); 38 | - add the Notion integration secret to your `.env.local` file. 39 | 3. Copy the [database ids](https://developers.notion.com/docs/create-a-notion-integration#step-3-save-the-database-id) and add them into your `.env.local` file. 40 | 4. Open notion in the web and open the network tab when signed in. Check request cookie: 41 | - copy token_v2 into your `.env.local` file; 42 | - copy notion_user_id into your `.env.local` file. 43 | 44 | ### 🚀 Deploy to Production 45 | 46 | I used [Vercel](https://vercel.com/home) to deploy my blog automatically every time I push to the `main` GitHub branch. See the [setup docs](https://nextjs.org/learn/basics/deploying-nextjs-app/deploy). 47 | 48 |
49 | 50 | ## ✨ Features 51 | - NextJS 13 [Server Components](https://nextjs.org/blog/next-13#new-app-directory-beta) and Tailwind 52 | - Mobile responsiveness 53 | - 404/500 error pages 54 | - Loading skeletons 55 | - Dark mode! 56 | 57 | ### 🏡 Home page 58 | ### ❓ About me page (optional) 59 | ### 📝 Blog (optional) 60 | A blog which pulls articles from your Notion database and renders the article content. Includes: 61 | - Search bar for articles by title/ tags 62 | - Renders embedded images & video 63 | - Renders code blocks & inline code 64 | - Renders Notion components (e.g. callouts) 65 | - Shows the date and article tags 66 | 67 | ### 📔 Dev journal (optional) 68 | A development journal to keep track of daily learnings. Includes: 69 | - Search bar for journal entries by title/ tags 70 | - Renders embedded images & video 71 | - Renders code blocks & inline code 72 | - Renders Notion components (e.g. callouts) 73 | - Shows the date and article tags 74 | 75 | ### 🎓 Resources (optional) 76 | A searchable, filterable list for recommended resources to track external resources you would recommend to others. Filter by resource type (Book, Article, Channel, Video, Newsletter, Website). 77 | 78 | ### 🤖 Technologies (optional) 79 | Show off what technologies/ tools you use. 80 | 81 | ### Acknowledgements 82 | The general UX of this site is inspired by [Lee Rob](https://leerob.io/). I liked it because it's a very clear, minimal design which also has some mobile responsiveness (which is a must-have). 83 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-notion-blog-builder", 3 | "version": "1.0.9", 4 | "description": "CLI tool for building a NextJS 13 blog with Notion as a CMS", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.js", 7 | "type": "module", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/JamesDHW/next-notion-blog-builder" 11 | }, 12 | "scripts": { 13 | "start": "ts-node-esm src/index.ts", 14 | "dev": "nodemon --watch src --exec yarn start", 15 | "build": "tsc --noEmit false && rm -rf dist/templates && cp -r src/templates dist" 16 | }, 17 | "bin": "./dist/index.js", 18 | "keywords": [ 19 | "notion", 20 | "blog", 21 | "cms", 22 | "portfolio", 23 | "next", 24 | "nextJS", 25 | "NextJS 13" 26 | ], 27 | "author": "James Haworth Wheatman", 28 | "license": "MIT", 29 | "engines": { 30 | "node": ">=14.16" 31 | }, 32 | "dependencies": { 33 | "chalk": "^5.2.0", 34 | "execa": "^6.1.0", 35 | "fs-extra": "^11.1.0", 36 | "inquirer": "^9.1.4" 37 | }, 38 | "devDependencies": { 39 | "@types/fs-extra": "^11.0.1", 40 | "@types/inquirer": "^9.0.3", 41 | "@types/node": "^18.11.18", 42 | "nodemon": "^2.0.20", 43 | "ts-node": "^10.9.1", 44 | "typescript": "^4.9.4" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/cli/index.ts: -------------------------------------------------------------------------------- 1 | import inquirer from "inquirer"; 2 | import { validateAppName } from './validation.js'; 3 | import { AVAILABLE_PAGES, DEFAULT_CLI_OPTIONS, PACKAGE_MANAGERS } from '../constants.js'; 4 | import { AvailablePages, CliResult, PackageManager } from '../types.js'; 5 | 6 | 7 | export const runCliForProjectSpec = async (): Promise => { 8 | const appName = await promptAppName() 9 | const pages = await promptPages() 10 | const packageManager = await promptPackageManager() 11 | 12 | return { 13 | appName: appName ?? DEFAULT_CLI_OPTIONS.appName, 14 | pages: pages ?? DEFAULT_CLI_OPTIONS.pages, 15 | packageManager: packageManager ?? DEFAULT_CLI_OPTIONS.packageManager, 16 | } 17 | } 18 | 19 | const promptAppName = async (): Promise => { 20 | const { appName } = await inquirer.prompt>({ 21 | name: "appName", 22 | type: "input", 23 | message: "Name your project:", 24 | default: DEFAULT_CLI_OPTIONS.appName, 25 | validate: validateAppName, 26 | transformer: (input: string) => { 27 | return input.trim(); 28 | }, 29 | }); 30 | 31 | return appName; 32 | }; 33 | 34 | const promptPages = async (): Promise => { 35 | const { pages } = await inquirer.prompt>({ 36 | name: "pages", 37 | type: "checkbox", 38 | message: "Which pages would you like to enable?", 39 | choices: AVAILABLE_PAGES 40 | .map((name) => ({ name, checked: true })), 41 | }); 42 | 43 | return pages; 44 | }; 45 | 46 | const promptPackageManager = async (): Promise => { 47 | const { packageManager } = await inquirer.prompt>({ 48 | name: "pages", 49 | type: "list", 50 | message: "Which package manager should we use?", 51 | choices: PACKAGE_MANAGERS, 52 | }); 53 | 54 | return packageManager; 55 | }; 56 | -------------------------------------------------------------------------------- /src/cli/validation.ts: -------------------------------------------------------------------------------- 1 | const validationRegExp = 2 | /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/; 3 | 4 | //Validate a string against allowed package.json names 5 | export const validateAppName = (input: string) => { 6 | const paths = input.split("/"); 7 | 8 | // If the first part is a @, it's a scoped package 9 | const indexOfDelimiter = paths.findIndex((p) => p.startsWith("@")); 10 | 11 | let appName = paths[paths.length - 1]; 12 | if (paths.findIndex((p) => p.startsWith("@")) !== -1) { 13 | appName = paths.slice(indexOfDelimiter).join("/"); 14 | } 15 | 16 | if (input === "." || validationRegExp.test(appName ?? "")) { 17 | return true; 18 | } else { 19 | return "App name must consist of only lowercase alphanumeric characters, '-', and '_'"; 20 | } 21 | }; -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | import { CliResult } from "./types.js"; 2 | 3 | export const AVAILABLE_PAGES = [ 4 | "about", 5 | "blog", 6 | "journal", 7 | "resources", 8 | "tech", 9 | ] as const; 10 | export const PACKAGE_MANAGERS = ["npm", "yarn"] as const; 11 | 12 | const DEFAULT_PROJECT_NAME = "next-notion-blog"; 13 | export const DEFAULT_CLI_OPTIONS: CliResult = { 14 | appName: DEFAULT_PROJECT_NAME, 15 | pages: ["about", "blog", "journal", "resources", "tech"], 16 | packageManager: "yarn", 17 | }; -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { runCliForProjectSpec } from "./cli/index.js"; 4 | import { 5 | copyOverBaseTemplate, 6 | copyOverPageTemplates, 7 | getRootDirectories, 8 | installDependencies, 9 | log, 10 | logError, 11 | logHighlight, 12 | logSuccess, 13 | } from "./utils.js"; 14 | import { pathExistsSync } from "fs-extra/esm"; 15 | 16 | 17 | 18 | const main = async () => { 19 | const { appName, pages, packageManager } = await runCliForProjectSpec(); 20 | const { templateRootDir, targetRootDir } = getRootDirectories(appName); 21 | 22 | if (pathExistsSync(targetRootDir)) 23 | throw new Error(`${appName} directory already exists!`) 24 | 25 | log( 26 | `\nGreat! Generating ${ 27 | logHighlight(appName) 28 | } for you with the pages: ${ 29 | logHighlight(pages.join(', ')) 30 | }!\n` 31 | ); 32 | 33 | copyOverBaseTemplate(templateRootDir, targetRootDir); 34 | copyOverPageTemplates(templateRootDir, targetRootDir, pages); 35 | 36 | log(`\nPages generated. Installing dependencies...\n`); 37 | 38 | await installDependencies(targetRootDir, packageManager) 39 | 40 | logSuccess(`\nDone! Enjoy your blog - complete the setup by following the readme.\n`); 41 | } 42 | 43 | main().catch(err => { 44 | logError("\nOops, something went wrong:"); 45 | logError(`${err}\n`) 46 | console.error(err) 47 | }) -------------------------------------------------------------------------------- /src/templates/base/.env.local.example: -------------------------------------------------------------------------------- 1 | NOTION_ACTIVE_USER= 2 | NOTION_TOKEN_V2= 3 | NOTION_API_INTEGRATION_SECRET= 4 | 5 | BLOG_DB_ID= 6 | JOURNAL_DB_ID= 7 | RESOURCES_DB_ID= -------------------------------------------------------------------------------- /src/templates/base/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "next/core-web-vitals", 4 | "eslint:recommended", 5 | "plugin:jsx-a11y/recommended", 6 | "plugin:prettier/recommended" 7 | ], 8 | "rules": { 9 | "react/jsx-uses-react": "off", 10 | "react/react-in-jsx-scope": "off", 11 | "curly": ["error", "all"], 12 | "eqeqeq": ["error", "smart"], 13 | "import/no-extraneous-dependencies": [ 14 | "error", 15 | { 16 | "devDependencies": true, 17 | "optionalDependencies": false, 18 | "peerDependencies": false 19 | } 20 | ], 21 | "no-shadow": [ 22 | "error", 23 | { 24 | "hoist": "all" 25 | } 26 | ], 27 | "@typescript-eslint/no-shadow": "error", 28 | "prefer-const": "error", 29 | "import/order": [ 30 | "error", 31 | { 32 | "groups": [ 33 | ["external", "builtin"], 34 | "internal", 35 | ["parent", "sibling", "index"] 36 | ] 37 | } 38 | ], 39 | "sort-imports": [ 40 | "error", 41 | { 42 | "ignoreCase": true, 43 | "ignoreDeclarationSort": true, 44 | "ignoreMemberSort": false, 45 | "memberSyntaxSortOrder": ["none", "all", "multiple", "single"] 46 | } 47 | ], 48 | "padding-line-between-statements": [ 49 | "error", 50 | { 51 | "blankLine": "always", 52 | "prev": "*", 53 | "next": "return" 54 | } 55 | ], 56 | "react/no-string-refs": "warn", 57 | "react-hooks/rules-of-hooks": "error", 58 | "react-hooks/exhaustive-deps": "warn", 59 | "@typescript-eslint/explicit-function-return-type": "off" 60 | }, 61 | "root": true, 62 | "plugins": ["import", "jsx-a11y"], 63 | "env": { 64 | "browser": true, 65 | "es6": true, 66 | "node": true 67 | }, 68 | "settings": { 69 | "react": { 70 | "version": "detect" 71 | } 72 | }, 73 | "overrides": [ 74 | { 75 | "files": ["**/*.ts?(x)"], 76 | "extends": [ 77 | "plugin:@typescript-eslint/recommended", 78 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 79 | "plugin:prettier/recommended" 80 | ], 81 | "parser": "@typescript-eslint/parser", 82 | "parserOptions": { 83 | "project": "tsconfig.json" 84 | }, 85 | "rules": { 86 | "@typescript-eslint/prefer-optional-chain": "error", 87 | "no-shadow": "off", 88 | "@typescript-eslint/no-shadow": "error", 89 | "@typescript-eslint/prefer-nullish-coalescing": "error", 90 | "@typescript-eslint/strict-boolean-expressions": [ 91 | "error", 92 | { 93 | "allowString": false, 94 | "allowNumber": false, 95 | "allowNullableObject": true 96 | } 97 | ], 98 | "@typescript-eslint/no-unnecessary-boolean-literal-compare": "error", 99 | "@typescript-eslint/no-unnecessary-condition": "error", 100 | "@typescript-eslint/no-unnecessary-type-arguments": "error", 101 | "@typescript-eslint/prefer-string-starts-ends-with": "error", 102 | "@typescript-eslint/switch-exhaustiveness-check": "error", 103 | "@typescript-eslint/restrict-template-expressions": [ 104 | "error", 105 | { 106 | "allowNumber": true, 107 | "allowBoolean": true 108 | } 109 | ], 110 | "import/no-anonymous-default-export": [ 111 | "error", 112 | { 113 | "allowArray": false, 114 | "allowArrowFunction": false, 115 | "allowAnonymousClass": false, 116 | "allowAnonymousFunction": false, 117 | "allowCallExpression": true, // The true value here is for backward compatibility 118 | "allowLiteral": true, 119 | "allowObject": false 120 | } 121 | ] 122 | } 123 | } 124 | ] 125 | } 126 | -------------------------------------------------------------------------------- /src/templates/base/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # next.js 10 | /.next/ 11 | /out/ 12 | 13 | # production 14 | /build 15 | 16 | # misc 17 | .DS_Store 18 | *.pem 19 | 20 | # debug 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | .pnpm-debug.log* 25 | 26 | # local env files 27 | .env*.local 28 | 29 | # vercel 30 | .vercel 31 | 32 | # typescript 33 | *.tsbuildinfo 34 | next-env.d.ts 35 | 36 | ## Re-add after init commit 37 | # .vscode 38 | 39 | -------------------------------------------------------------------------------- /src/templates/base/.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 18.7.0 2 | -------------------------------------------------------------------------------- /src/templates/base/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "streetsidesoftware.code-spell-checker", 4 | "dbaeumer.vscode-eslint", 5 | "esbenp.prettier-vscode", 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /src/templates/base/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": false, // This makes sure we don"t have conflicts between eslint and prettier. 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": true, 5 | "source.fixAll.markdownlint": true 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /src/templates/base/README.md: -------------------------------------------------------------------------------- 1 | ## My Website 2 | -------------------------------------------------------------------------------- /src/templates/base/app/constants.ts: -------------------------------------------------------------------------------- 1 | 2 | import { NavItemPops } from "components/common/Navbar/NavItem"; 3 | 4 | // NEEDS UPDATING BASED ON PAGES GENERATED 5 | 6 | export const PATHS = { 7 | HOME: "/", 8 | ABOUT: "/about", 9 | BLOG: "/blog", 10 | JOURNAL: "/journal", 11 | TECH: "/tech", 12 | RESOURCES: "/resources", 13 | }; 14 | 15 | export const NAVBAR_ITEMS: NavItemPops[] = [ 16 | { 17 | href: PATHS.HOME, 18 | label: "Home", 19 | }, 20 | { 21 | href: PATHS.ABOUT, 22 | label: "About Me", 23 | }, 24 | { 25 | href: PATHS.BLOG, 26 | label: "Blog", 27 | }, 28 | { 29 | href: PATHS.JOURNAL, 30 | label: "Dev Journal", 31 | }, 32 | ]; 33 | -------------------------------------------------------------------------------- /src/templates/base/app/error.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Link from "next/link"; 4 | 5 | export default function Error() { 6 | return ( 7 |
8 |
9 |

500

10 |

~ Something went wrong ~

11 |
12 | 16 | Return Home 17 | 18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /src/templates/base/app/head.tsx: -------------------------------------------------------------------------------- 1 | export default function Head() { 2 | return ( 3 | <> 4 | 5 | 9 | {`<your-name-here>`} 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/templates/base/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { FC, ReactNode } from "react"; 2 | import { ThemeProvider } from "styles/ThemeProvider"; 3 | import { Navbar } from "components/common/Navbar"; 4 | import { Footer } from "components/common/Footer"; 5 | import "styles/globals.css"; 6 | 7 | // Notion CSS 8 | import "react-notion-x/src/styles.css"; 9 | import "katex/dist/katex.min.css"; 10 | import "prismjs/themes/prism-tomorrow.css"; 11 | 12 | type RootPageProps = { children: ReactNode }; 13 | 14 | const RootLayout: FC = ({ children }) => { 15 | return ( 16 | 17 | 18 | 19 | 20 |
21 | 22 | {children} 23 |
24 |