├── .eslintrc.json ├── .gitignore ├── .prettierrc.js ├── LICENSE ├── README.md ├── components ├── ArrowIcon.js ├── CustomImage.js ├── CustomLink.js ├── Footer.js ├── Header.js ├── Layout.js ├── Layout.module.css └── SEO.js ├── github-banner.svg ├── netlify.toml ├── next-env.d.ts ├── nextjs-blog-theme-preview.png ├── nextjs-setup-wizard.png ├── package-lock.json ├── package.json ├── pages ├── _app.js ├── _document.js ├── index.js └── posts │ └── [slug].js ├── postcss.config.js ├── posts ├── example-post-1.mdx ├── example-post-2.mdx ├── example-post-3.mdx ├── example-post-4.mdx └── example-post-5.mdx ├── public ├── favicon.svg └── images │ ├── nextjs.svg │ └── random-image.jpeg ├── renovate.json ├── stackbit.config.ts ├── styles └── globals.css ├── themes.js ├── tsconfig.json └── utils ├── global-data.js ├── mdx-utils.js └── theme-utils.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "prettier"] 3 | } 4 | -------------------------------------------------------------------------------- /.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 | # Netlify Visual Editor (formerly Stackbit) 9 | .sourcebit-nextjs-cache.json 10 | .stackbit/cache 11 | .cache 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | 32 | # local env files 33 | .env.local 34 | .env.development.local 35 | .env.test.local 36 | .env.production.local 37 | 38 | # vercel 39 | .vercel 40 | 41 | # netlify 42 | .netlify -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | semi: true, 4 | singleQuote: true, 5 | tabWidth: 2, 6 | useTabs: false, 7 | }; 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 nextjs-blog-theme 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Netlify Next.js Blog Template designed by Bejamas](https://user-images.githubusercontent.com/43764894/223762618-62742b4e-9424-44a7-8e85-9f7e4e19db54.png) 2 | 3 | 4 | [![Deploy to Netlify Button](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/netlify-templates/nextjs-blog-theme) 5 | 6 | 7 | A customizable blog starter using: 8 | 9 | - [Next.js](https://github.com/vercel/next.js) v15 (Pages Router) 10 | - [Tailwind](https://tailwindcss.com/) v4.x 11 | - [Netlify Visual Editor](https://docs.netlify.com/visual-editor/overview/) 12 | - Built-in [MDX](https://mdxjs.com/) support 13 | - Includes modern design with dark & light themes 14 | 15 | ![Preview of blog theme. Author named Jay Doe and blog's name is "Next.js Blog Theme" with one blog post](nextjs-blog-theme-preview.png) 16 | 17 | [Take a gander at the demo.](https://bejamas-nextjs-blog.netlify.app) 18 | 19 | [Click here to watch the template walkthrough!](https://www.youtube.com/watch?v=63QZHs259dY) 20 | 21 | ## Table of Contents: 22 | 23 | - [Getting Started](#getting-started) 24 | - [Setting Up Locally](#setting-up-locally) 25 | - [Using the Wizard](#using-the-setup-wizard) 26 | - [Configuring the Blog](#configuring-the-blog) 27 | - [Adding New Posts](#adding-new-posts) 28 | - [Netlify Visual Editor](#netlify-visual-editor) 29 | - [Testing](#testing) 30 | - [Included Default Testing](#included-default-testing) 31 | - [Removing Renovate](#removing-renovate) 32 | 33 | ## Getting Started 34 | 35 | --- 36 | 37 | You can get started with this project in two ways: locally or using the [setup wizard](https://nextjs-wizard.netlify.app/). 38 | 39 | ### Setting Up Locally 40 | 41 | If you're doing it locally, start with clicking the [use this template](https://github.com/netlify-templates/nextjs-blog-theme/generate) button on GitHub. This will create a new repository with this template's files on your GitHub account. Once that is done, clone your new repository and navigate to it in your terminal. 42 | 43 | From there, you can install the project's dependencies by running: 44 | 45 | ```shell 46 | yarn install 47 | ``` 48 | 49 | Finally, you can run your project locally with: 50 | 51 | ```shell 52 | yarn run dev 53 | ``` 54 | 55 | Open your browser and visit , your project should be running! 56 | 57 | ### Using the Setup Wizard 58 | 59 | ![Preview of Setup Wizard showing the initial page of a setup form](nextjs-setup-wizard.png) 60 | 61 | Through the [setup wizard](https://nextjs-wizard.netlify.app/), you can create your blog in a few clicks and deploy to Netlify. 62 | 63 | ## Configuring the blog 64 | 65 | The config is based on environment variables to make it easy to integrate with any Jamstack platform, like Netlify. 66 | 67 | Here are the variables you can edit: 68 | | Variable | Description | Options 69 | | --- | --- | --- | 70 | | `BLOG_NAME` | the name of your blog, displayed below the avatar || 71 | | `BLOG_TITLE` | the main header (`h1`) on the home page || 72 | | `BLOG_FOOTER_TEXT`| the text in the footer || 73 | | `BLOG_THEME` | the theme to pass to Tailwind | default | 74 | | `BLOG_FONT_HEADINGS` | the font-family for all HTML headings, from `h1` to `h6`| sans-serif (default), serif, monospace| 75 | | `BLOG_FONT_PARAGRAPHS` | the font-family for all other HTML elements | sans-serif (default), serif, monospace| 76 | 77 | All of the env variables can be configured through the [Wizard](https://nextjs-wizard.netlify.app/) or through setting the project's environment variables. You can do this in your Netlify dashaboard (Site settings/Build & deploy/Environment/Environment variables). 78 | 79 | https://user-images.githubusercontent.com/3611928/153997545-6dcdeef0-e570-49e7-93d6-ce0d393d16c9.mp4 80 | 81 | [alt: video walkthrough of editing env vars] 82 | 83 | If setting an environment variable isn't your cup of tea, the defaults can be changed in [`utils/global-data.js`](/utils/global-data.js). You can also remove the variables and hard code blog information where these variables are used in the code base. 84 | 85 | - `BLOG_THEME, BLOG_FONT_HEADINGS, & BLOG_FONT_PARAGRAPHS` are used in [`tailwind-preset.js`](tailwind-preset.js) 86 | - `BLOG_NAME, BLOG_TITLE, BLOG_FOOTER_TEXT` are used in [`pages/index.js`](pages/index.js) & [`pages/posts/[slug].js`](pages/posts/[slug].js) through the `globalData` object. 87 | 88 | ## Adding new posts 89 | 90 | All posts are stored in `/posts` directory. To make a new post, create a new file with the [`.mdx` extension](https://mdxjs.com/). 91 | 92 | Since the posts are written in `MDX` format you can pass props and components. That means you can use [React components](https://reactjs.org/docs/components-and-props.html) inside your posts to make them more interactive. Learn more about how to do so in the [MDX docs on content](https://mdxjs.com/docs/using-mdx/#components). 93 | 94 | https://user-images.githubusercontent.com/3611928/152727802-102ec296-41c8-446d-93ed-922d11187073.mp4 95 | 96 | [alt: video walkthrough of adding a new blog post] 97 | 98 | ## Netlify Visual Editor 99 | 100 | This template is configured to work with [visual editing](https://docs.netlify.com/visual-editor/overview/) and [Git Content Source](https://docs.netlify.com/create/content-sources/git/). 101 | 102 | ### Develop with Netlify Visual Editor Locally 103 | 104 | The typical development process is to begin by working locally. Clone this repository, then run `npm install` in its root directory. 105 | 106 | Run the Next.js development server: 107 | 108 | ```txt 109 | cd nextjs-blog-theme 110 | npm run dev 111 | ``` 112 | 113 | Install the [Netlify Visual Editor CLI](https://www.npmjs.com/package/@stackbit/cli). Then open a new terminal window in the same project directory and run the Netlify visual editor dev server: 114 | 115 | ```txt 116 | npm install -g @stackbit/cli 117 | stackbit dev 118 | ``` 119 | 120 | This outputs your own Netlify visual editor URL. Open this, register, or sign in, and you will be directed to Netlify's visual editor for your new project. 121 | 122 | ![Next.js Dev + Visual Editor Dev](https://assets.stackbit.com/docs/next-dev-stackbit-dev.png) 123 | 124 | ### Next Steps 125 | 126 | Here are a few suggestions on what to do next if you're new to Netlify Visual Editor: 127 | 128 | - Learn [Netlify visual editor overview](https://docs.netlify.com/visual-editor/visual-editing/) 129 | - Check [Netlify visual editor reference documentation](https://visual-editor-reference.netlify.com/) 130 | 131 | ## Testing 132 | 133 | ### Included Default Testing 134 | 135 | We’ve included some tooling that helps us maintain these templates. This template currently uses: 136 | 137 | - [Renovate](https://www.mend.io/free-developer-tools/renovate/) - to regularly update our dependencies 138 | 139 | If your team is not interested in this tooling, you can remove them with ease! 140 | 141 | ### Removing Renovate 142 | 143 | In order to keep our project up-to-date with dependencies we use a tool called [Renovate](https://github.com/marketplace/renovate). If you’re not interested in this tooling, delete the `renovate.json` file and commit that onto your main branch. 144 | 145 | ## Support 146 | 147 | If you get stuck along the way, get help in our [support forums](https://answers.netlify.com/). 148 | -------------------------------------------------------------------------------- /components/ArrowIcon.js: -------------------------------------------------------------------------------- 1 | export default function ArrowIcon({ className, color = 'text-primary' }) { 2 | return ( 3 | 11 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /components/CustomImage.js: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | 3 | export default function CustomImage({ src, alt, ...otherProps }) { 4 | return ( 5 |
6 | {alt 13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /components/CustomLink.js: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | 3 | export default function CustomLink({ as, href, ...otherProps }) { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /components/Footer.js: -------------------------------------------------------------------------------- 1 | const sunIcon = ( 2 | 10 | 17 | 18 | 19 | 20 | 21 | 26 | 27 | 28 | 29 | ); 30 | 31 | const moonIcon = ( 32 | 39 | 47 | 48 | ); 49 | 50 | const ThemeSwitcher = () => { 51 | return ( 52 |
53 | 64 | 65 | 76 |
77 | ); 78 | }; 79 | 80 | export default function Footer({ copyrightText }) { 81 | return ( 82 | 88 | ); 89 | } 90 | -------------------------------------------------------------------------------- /components/Header.js: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | 3 | export default function Header({ name }) { 4 | return ( 5 |
6 |
7 |

8 | {name} 9 |

10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /components/Layout.js: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import { useEffect } from 'react'; 3 | import styles from './Layout.module.css'; 4 | 5 | export function GradientBackground({ variant, className }) { 6 | const classes = classNames( 7 | { 8 | [styles.colorBackground]: variant === 'large', 9 | [styles.colorBackgroundBottom]: variant === 'small', 10 | }, 11 | className 12 | ); 13 | 14 | return
; 15 | } 16 | 17 | export default function Layout({ children }) { 18 | const setAppTheme = () => { 19 | const darkMode = localStorage.getItem('theme') === 'dark'; 20 | const lightMode = localStorage.getItem('theme') === 'light'; 21 | 22 | if (darkMode) { 23 | document.documentElement.classList.add('dark'); 24 | } else if (lightMode) { 25 | document.documentElement.classList.remove('dark'); 26 | } 27 | return; 28 | }; 29 | 30 | const handleSystemThemeChange = () => { 31 | var darkQuery = window.matchMedia('(prefers-color-scheme: dark)'); 32 | 33 | darkQuery.onchange = (e) => { 34 | if (e.matches) { 35 | document.documentElement.classList.add('dark'); 36 | localStorage.setItem('theme', 'dark'); 37 | } else { 38 | document.documentElement.classList.remove('dark'); 39 | localStorage.setItem('theme', 'light'); 40 | } 41 | }; 42 | }; 43 | 44 | useEffect(() => { 45 | setAppTheme(); 46 | }, []); 47 | 48 | useEffect(() => { 49 | handleSystemThemeChange(); 50 | }, []); 51 | 52 | return ( 53 |
54 |
55 | {children} 56 |
57 |
58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /components/Layout.module.css: -------------------------------------------------------------------------------- 1 | .colorBackground { 2 | left: 50%; 3 | transform: translateX(-50%); 4 | background: radial-gradient( 5 | at 71% 77%, 6 | var(--color-gradient-1) 0, 7 | transparent 21% 8 | ), 9 | radial-gradient(at 36% 47%, var(--color-gradient-3) 0, transparent 50%), 10 | radial-gradient(at 54% 29%, var(--color-gradient-3) 0, transparent 28%), 11 | radial-gradient(at 45% 51%, var(--color-gradient-1) 0, transparent 53%), 12 | radial-gradient(at 73% 44%, var(--color-gradient-2) 0, transparent 54%), 13 | radial-gradient(at 24% 7%, var(--color-gradient-2) 0, transparent 40%), 14 | radial-gradient(at 76% 46%, var(--color-gradient-1) 0, transparent 50%); 15 | /* mix-blend-mode: normal; */ 16 | max-height: 800px; 17 | height: 80vh; 18 | max-width: 1400px; 19 | width: 70vw; 20 | width: 100%; 21 | filter: blur(44px); 22 | z-index: -1; 23 | } 24 | 25 | .colorBackgroundBottom { 26 | left: 50%; 27 | transform: translateX(-50%) rotate(190deg); 28 | background: radial-gradient( 29 | at 83% 25%, 30 | var(--color-gradient-1) 0, 31 | transparent 21% 32 | ), 33 | radial-gradient(at 36% 47%, var(--color-gradient-3) 0, transparent 50%), 34 | radial-gradient(at 79% 45%, var(--color-gradient-3) 0, transparent 28%), 35 | radial-gradient(at 66% 38%, var(--color-gradient-1) 0, transparent 53%), 36 | radial-gradient(at 89% 13%, var(--color-gradient-2) 0, transparent 54%), 37 | radial-gradient(at 24% 7%, var(--color-gradient-2) 0, transparent 40%), 38 | radial-gradient(at 76% 46%, var(--color-gradient-1) 0, transparent 50%); 39 | /* mix-blend-mode: normal; */ 40 | height: 600px; 41 | max-width: 900px; 42 | width: 55vw; 43 | width: 100%; 44 | filter: blur(44px); 45 | z-index: -1; 46 | } 47 | -------------------------------------------------------------------------------- /components/SEO.js: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | 3 | export default function SEO({ title, description }) { 4 | return ( 5 | 6 | {title} 7 | 8 | 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /github-banner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | command = "npm run build" 3 | publish = ".next" 4 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/pages/api-reference/config/typescript for more information. 6 | -------------------------------------------------------------------------------- /nextjs-blog-theme-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/nextjs-blog-theme/cb8b71144b2f80ac4f653cdb4ad7db3000bee591/nextjs-blog-theme-preview.png -------------------------------------------------------------------------------- /nextjs-setup-wizard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/nextjs-blog-theme/cb8b71144b2f80ac4f653cdb4ad7db3000bee591/nextjs-setup-wizard.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-blog-theme", 3 | "description": "A customizable Next.js and Tailwind blog starter. Designed by the Bejamas agency.", 4 | "author": "Bejamas and Netlify Inc.", 5 | "contributors": [ 6 | "Charlie Gerard", 7 | "Prince Wilson", 8 | "Tara Manicsic", 9 | "Thom Krupa", 10 | "Tomas Bankauskas" 11 | ], 12 | "homepage": "https://github.com/netlify-templates/nextjs-blog-theme", 13 | "bugs": { 14 | "url": "https://github.com/netlify-templates/nextjs-blog-theme/issues" 15 | }, 16 | "scripts": { 17 | "dev": "next", 18 | "dev:watch": "next-remote-watch ./posts", 19 | "build": "next build", 20 | "start": "next start", 21 | "export": "next build && next export", 22 | "lint": "next lint" 23 | }, 24 | "dependencies": { 25 | "@mapbox/rehype-prism": "^0.9.0", 26 | "@tailwindcss/typography": "^0.5.12", 27 | "classnames": "^2.3.1", 28 | "gray-matter": "^4.0.3", 29 | "next": "^15.1.4", 30 | "next-mdx-remote": "^5.0.0", 31 | "next-remote-watch": "2.0.0", 32 | "prismjs": "^1.29.0", 33 | "react": "^18.2.0", 34 | "react-dom": "^18.2.0", 35 | "rehype-unwrap-images": "^1.0.0", 36 | "remark-gfm": "^4.0.0" 37 | }, 38 | "devDependencies": { 39 | "@stackbit/cms-git": "^1.0.32", 40 | "@stackbit/types": "^2.0.1", 41 | "@tailwindcss/postcss": "^4.0.16", 42 | "eslint": "^8.57.0", 43 | "eslint-config-next": "^15.1.4", 44 | "eslint-config-prettier": "^9.1.0", 45 | "postcss": "^8.4.38", 46 | "tailwindcss": "^4.0.16", 47 | "typescript": "^5.1.3" 48 | }, 49 | "license": "MIT" 50 | } 51 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css'; 2 | import 'prismjs/themes/prism-tomorrow.css'; 3 | 4 | function MyApp({ Component, pageProps }) { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | ); 11 | } 12 | 13 | export default MyApp; 14 | -------------------------------------------------------------------------------- /pages/_document.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Document, { Html, Head, Main, NextScript } from 'next/document'; 3 | import { generateCssVariables } from '../utils/theme-utils'; 4 | 5 | class MyDocument extends Document { 6 | render() { 7 | const cssVars = generateCssVariables(); 8 | 9 | return ( 10 | 11 | 12 | 13 | 14 | 17 |
18 | 19 | 20 | 21 | ); 22 | } 23 | } 24 | 25 | export default MyDocument; 26 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import { getPosts } from '../utils/mdx-utils'; 3 | 4 | import Footer from '../components/Footer'; 5 | import Header from '../components/Header'; 6 | import Layout, { GradientBackground } from '../components/Layout'; 7 | import ArrowIcon from '../components/ArrowIcon'; 8 | import { getGlobalData } from '../utils/global-data'; 9 | import SEO from '../components/SEO'; 10 | 11 | export default function Index({ posts, globalData }) { 12 | return ( 13 | 14 | 15 |
16 |
17 |

18 | {globalData.blogTitle} 19 |

20 |
    21 | {posts.map((post) => ( 22 |
  • 27 | 32 | {post.data.date && ( 33 |

    37 | {post.data.date} 38 |

    39 | )} 40 |

    41 | {post.data.title} 42 |

    43 | {post.data.description && ( 44 |

    48 | {post.data.description} 49 |

    50 | )} 51 | 52 | 53 |
  • 54 | ))} 55 |
56 |
57 |