├── .prettierrc
├── .prettierignore
├── public
├── code-review-bro.png
├── favicon
│ ├── favicon.ico
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── apple-touch-icon.png
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ └── site.webmanifest
├── robots.txt
├── vercel.svg
├── sitemap.xml
└── bighead.svg
├── next-sitemap.js
├── src
├── theme
│ ├── colorMode.jsx
│ └── index.jsx
├── components
│ ├── UI
│ │ ├── logo.jsx
│ │ ├── hamburgerMenu.jsx
│ │ ├── tag.jsx
│ │ ├── colorModeToggle.jsx
│ │ └── tagColor.jsx
│ ├── MDXComponents
│ │ ├── styles
│ │ │ └── codeBlock.module.css
│ │ ├── headings.jsx
│ │ ├── index.jsx
│ │ ├── common.jsx
│ │ └── codeBlock.jsx
│ ├── header
│ │ ├── index.jsx
│ │ └── navbar.jsx
│ ├── blogPost.jsx
│ └── footer.jsx
├── data
│ └── blog
│ │ ├── portfolio-boilerplate-nextjs-introducing.mdx
│ │ ├── pre-rendering.mdx
│ │ ├── ssg-ssr.mdx
│ │ ├── markdown-examples.mdx
│ │ └── lorem-ipsum.mdx
├── layouts
│ └── global.jsx
├── pages
│ ├── _app.jsx
│ ├── _document.jsx
│ ├── blog.jsx
│ ├── index.jsx
│ └── blog
│ │ └── [slug].jsx
├── styles
│ └── index.css
└── lib
│ └── posts.js
├── next-seo.config.js
├── next.config.js
├── jsconfig.json
├── .github
└── dependabot.yml
├── .gitignore
├── config.js
├── package.json
└── README.md
/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .next
2 |
--------------------------------------------------------------------------------
/public/code-review-bro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imadatyatalah/nextjs-chakra-ui-portfolio-template/HEAD/public/code-review-bro.png
--------------------------------------------------------------------------------
/public/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imadatyatalah/nextjs-chakra-ui-portfolio-template/HEAD/public/favicon/favicon.ico
--------------------------------------------------------------------------------
/next-sitemap.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | siteUrl: "https://portfolio-boilerplate-nextjs.vercel.app",
3 | generateRobotsTxt: true,
4 | };
5 |
--------------------------------------------------------------------------------
/public/favicon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imadatyatalah/nextjs-chakra-ui-portfolio-template/HEAD/public/favicon/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imadatyatalah/nextjs-chakra-ui-portfolio-template/HEAD/public/favicon/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicon/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imadatyatalah/nextjs-chakra-ui-portfolio-template/HEAD/public/favicon/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicon/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imadatyatalah/nextjs-chakra-ui-portfolio-template/HEAD/public/favicon/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/favicon/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imadatyatalah/nextjs-chakra-ui-portfolio-template/HEAD/public/favicon/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # *
2 | User-agent: *
3 | Allow: /
4 |
5 | # Host
6 | Host: https://portfolio-boilerplate-nextjs.vercel.app
7 |
8 | # Sitemaps
9 | Sitemap: https://portfolio-boilerplate-nextjs.vercel.app/sitemap.xml
10 |
--------------------------------------------------------------------------------
/src/theme/colorMode.jsx:
--------------------------------------------------------------------------------
1 | import { extendTheme } from "@chakra-ui/react";
2 |
3 | const config = {
4 | initialColorMode: "light",
5 | useSystemColorMode: false,
6 | };
7 |
8 | const colorMode = extendTheme({ config });
9 |
10 | export default colorMode;
11 |
--------------------------------------------------------------------------------
/next-seo.config.js:
--------------------------------------------------------------------------------
1 | import { seo } from "config";
2 |
3 | export default {
4 | titleTemplate: `%s | ${seo.title}`,
5 | openGraph: {
6 | type: "website",
7 | locale: "en_US",
8 | url: seo.canonical,
9 | site_name: seo.title,
10 | },
11 | };
12 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const withMDX = require("@next/mdx")({
2 | extension: /\.mdx?$/,
3 | });
4 |
5 | /** @type {import('next').NextConfig} */
6 | const nextConfig = {
7 | pageExtensions: ["js", "jsx", "mdx"],
8 | reactStrictMode: true,
9 | swcMinify: true,
10 | };
11 |
12 | module.exports = withMDX(nextConfig);
13 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "@/components/*": ["src/components/*"],
6 | "@/layouts/*": ["src/layouts/*"],
7 | "@/theme/*": ["src/theme/*"],
8 | "@/lib/*": ["src/lib/*"],
9 | "@/styles/*": ["src/styles/*"]
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/components/UI/logo.jsx:
--------------------------------------------------------------------------------
1 | import { chakra } from "@chakra-ui/react";
2 | import NextLink from "next/link";
3 |
4 | const Logo = () => {
5 | return (
6 |
7 |
8 | LOGO
9 |
10 |
11 | );
12 | };
13 |
14 | export default Logo;
15 |
--------------------------------------------------------------------------------
/src/components/MDXComponents/styles/codeBlock.module.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | font-size: 14px;
3 | border: 1px solid gray;
4 | overflow: auto;
5 | padding: 0.5em;
6 | }
7 |
8 | .lineNo {
9 | display: table-cell;
10 | text-align: right;
11 | padding-right: 1em;
12 | user-select: none;
13 | opacity: 0.5;
14 | }
15 |
16 | .lineContent {
17 | display: table-cell;
18 | }
19 |
--------------------------------------------------------------------------------
/src/theme/index.jsx:
--------------------------------------------------------------------------------
1 | import { extendTheme } from "@chakra-ui/react";
2 |
3 | const styles = {
4 | global: {
5 | "html, body": {
6 | fontSize: "18px",
7 | },
8 | },
9 | };
10 |
11 | const fonts = {
12 | heading: "Poppins, -apple-system",
13 | body: "Poppins, -apple-system",
14 | };
15 |
16 | const theme = extendTheme({
17 | styles,
18 | fonts,
19 | });
20 |
21 | export default theme;
22 |
--------------------------------------------------------------------------------
/src/data/blog/portfolio-boilerplate-nextjs-introducing.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Introducing Next.js Portfolio Templeta Next.js"
3 | publishedAt: "2021-04-26"
4 | modifiedAt: "2021-04-26"
5 | author: "Imad Atyat-Alah"
6 | summary: "Looking for a performant beautiful portfolio template? Checkout the Next.js portfolio template. Created with Next.js and styled with Chakra-UI."
7 | tags: ["nextjs", "javascript"]
8 | ---
9 |
10 | To Be Done Soon.
11 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | # Fetch and update latest `npm` packages
4 | - package-ecosystem: npm
5 | directory: "/"
6 | schedule:
7 | interval: monthly
8 | time: "00:00"
9 | open-pull-requests-limit: 10
10 | reviewers:
11 | - imadatyatalah
12 | assignees:
13 | - imadatyatalah
14 | commit-message:
15 | prefix: fix
16 | prefix-development: chore
17 | include: scope
18 |
--------------------------------------------------------------------------------
/src/layouts/global.jsx:
--------------------------------------------------------------------------------
1 | import { Box } from "@chakra-ui/react";
2 |
3 | import { MAX_WIDTH } from "config";
4 | import Header from "@/components/header";
5 | import Footer from "@/components/footer";
6 |
7 | const Layout = ({ children }) => (
8 | <>
9 |
10 |
11 | {children}
12 |
13 |
14 | >
15 | );
16 |
17 | export default Layout;
18 |
--------------------------------------------------------------------------------
/src/components/UI/hamburgerMenu.jsx:
--------------------------------------------------------------------------------
1 | import { Box } from "@chakra-ui/react";
2 | import { Turn as Hamburger } from "hamburger-react";
3 |
4 | const HamburgerMenu = ({ toggled, toggle }) => {
5 | return (
6 |
7 |
15 |
16 | );
17 | };
18 |
19 | export default HamburgerMenu;
20 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/src/components/UI/tag.jsx:
--------------------------------------------------------------------------------
1 | import { Tag } from "@chakra-ui/react";
2 |
3 | const TagComponent = ({ children, color, ...props }) => {
4 | return (
5 |
19 | {children}
20 |
21 | );
22 | };
23 |
24 | export default TagComponent;
25 |
--------------------------------------------------------------------------------
/public/favicon/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "portfolio-boilerplate-nextjs",
3 | "short_name": "Next.js Portfolio",
4 | "icons": [
5 | {
6 | "src": "/apple-touche-icon.png",
7 | "sizes": "180x180",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/android-chrome-192x192.png",
12 | "sizes": "192x192",
13 | "type": "image/png"
14 | },
15 | {
16 | "src": "/android-chrome-512x512.png",
17 | "sizes": "512x512",
18 | "type": "image/png"
19 | }
20 | ],
21 | "theme_color": "#ffffff",
22 | "background_color": "#ffffff",
23 | "display": "standalone"
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/header/index.jsx:
--------------------------------------------------------------------------------
1 | import { Box } from "@chakra-ui/react";
2 |
3 | import { MAX_WIDTH } from "config";
4 | import Logo from "../UI/logo";
5 | import Navbar from "./navbar";
6 |
7 | const Header = () => {
8 | return (
9 |
10 |
19 |
20 |
21 |
22 |
23 | );
24 | };
25 |
26 | export default Header;
27 |
--------------------------------------------------------------------------------
/src/pages/_app.jsx:
--------------------------------------------------------------------------------
1 | import { ChakraProvider } from "@chakra-ui/react";
2 | import { DefaultSeo } from "next-seo";
3 |
4 | import theme from "@/theme/index";
5 | import Layout from "@/layouts/global";
6 | import SEO from "next-seo.config";
7 |
8 | import "@fontsource/poppins/latin-400.css";
9 | import "@fontsource/poppins/latin-500.css";
10 | import "@fontsource/poppins/latin-600.css";
11 | import "@fontsource/poppins/latin-700.css";
12 | import "@/styles/index.css";
13 |
14 | const MyApp = ({ Component, pageProps }) => (
15 | <>
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | >
24 | );
25 |
26 | export default MyApp;
27 |
--------------------------------------------------------------------------------
/src/components/UI/colorModeToggle.jsx:
--------------------------------------------------------------------------------
1 | import { Button, useColorMode } from "@chakra-ui/react";
2 | import { MoonIcon, SunIcon } from "@chakra-ui/icons";
3 |
4 | const DarkModeToggle = () => {
5 | const { colorMode, toggleColorMode } = useColorMode();
6 |
7 | return (
8 |
23 | );
24 | };
25 |
26 | export default DarkModeToggle;
27 |
--------------------------------------------------------------------------------
/src/components/MDXComponents/headings.jsx:
--------------------------------------------------------------------------------
1 | import { Box, chakra, Heading, VisuallyHidden } from "@chakra-ui/react";
2 |
3 | const Headings = (props) => {
4 | const { children, id } = props;
5 |
6 | return (
7 |
8 |
9 | Read the {children} section.
10 | {children}
11 | {id && (
12 |
22 | #
23 |
24 | )}
25 |
26 |
27 | );
28 | };
29 |
30 | export default Headings;
31 |
--------------------------------------------------------------------------------
/src/components/MDXComponents/index.jsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 |
3 | import {
4 | CustomLink,
5 | UnorderedList,
6 | OrderedList,
7 | ListItem,
8 | InlineCode,
9 | } from "./common";
10 | import Headings from "./headings";
11 | import CodeBlock from "./codeBlock";
12 |
13 | const MDXComponents = {
14 | h1: (props) => ,
15 | h2: (props) => ,
16 | h3: (props) => ,
17 | h4: (props) => ,
18 | h5: (props) => ,
19 | h6: (props) => ,
20 |
21 | code: CodeBlock,
22 | a: CustomLink,
23 | ul: UnorderedList,
24 | ol: OrderedList,
25 | li: ListItem,
26 | inlineCode: InlineCode,
27 | Image,
28 | };
29 |
30 | export default MDXComponents;
31 |
--------------------------------------------------------------------------------
/src/data/blog/pre-rendering.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Two Forms of Pre-rendering"
3 | publishedAt: "2020-01-01"
4 | modifiedAt: "2021-3-13"
5 | author: "author"
6 | summary: "Next.js has two forms of pre-rendering: Static Generation and Server-side Rendering..."
7 | tags: ["nextjs", "ssr", "optimization", "asynchronous"]
8 | ---
9 |
10 | Next.js has two forms of pre-rendering: **Static Generation** and **Server-side Rendering**. The difference is in **when** it generates the HTML for a page.
11 |
12 | - **Static Generation** is the pre-rendering method that generates the HTML at **build time**. The pre-rendered HTML is then _reused_ on each request.
13 | - **Server-side Rendering** is the pre-rendering method that generates the HTML on **each request**.
14 |
15 | Importantly, Next.js lets you **choose** which pre-rendering form to use for each page. You can create a "hybrid" Next.js app by using Static Generation for most pages and using Server-side Rendering for others.
16 |
--------------------------------------------------------------------------------
/src/components/UI/tagColor.jsx:
--------------------------------------------------------------------------------
1 | export const tagColor = {
2 | nextjs: { bgColor: "#0A7B83E2", hover: "#09686dE2" },
3 | javascript: { bgColor: "#ffba08", hover: "#faa307" },
4 | typescript: { bgColor: "#3178C6E2", hover: "#265d99E2" },
5 | api: { bgColor: "#E8265EE2", hover: "#ce0840E2" },
6 | asynchronous: { bgColor: "#1A91DAE2", hover: "#0b7bbcE2" },
7 | react: { bgColor: "#b2475dE2", hover: "#933346E2" },
8 | tutorial: { bgColor: "#865DCAE2", hover: "#784cc4E2" },
9 | design: { bgColor: "#f11df1E2", hover: "#d313d3E2" },
10 | html: { bgColor: "#f06529", hover: "#e34c26" },
11 | nodejs: { bgColor: "#ff7bacE2", hover: "#f7659aE2" },
12 | ssr: { bgColor: "#77aa7bE2", hover: "#609363E2" },
13 | css: { bgColor: "#FE4A49E2", hover: "#ed3434E2" },
14 | testing: { bgColor: "#05afa0E2", hover: "#0ce5d2E2" },
15 | webpack: { bgColor: "#a02438", hover: "#a02438E2" },
16 | optimization: { bgColor: "#8f4dbf", hover: "#8f4dbfE2" },
17 | markdown: { bgColor: "#168aad", hover: "#1a759f" },
18 | };
19 |
--------------------------------------------------------------------------------
/src/components/MDXComponents/common.jsx:
--------------------------------------------------------------------------------
1 | import { Link as ChakraLink, chakra, Code } from "@chakra-ui/react";
2 | import NextLink from "next/link";
3 |
4 | export const CustomLink = (props) => {
5 | const href = props.href;
6 | const isInternalLink = href && (href.startsWith("/") || href.startsWith("#"));
7 |
8 | if (isInternalLink) {
9 | return (
10 |
11 |
12 |
13 | );
14 | }
15 |
16 | return ;
17 | };
18 |
19 | export const UnorderedList = (props) => (
20 |
21 | );
22 |
23 | export const OrderedList = (props) => (
24 |
25 | );
26 |
27 | export const ListItem = (props) => (
28 |
29 | );
30 |
31 | export const InlineCode = (props) => ;
32 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/styles/index.css:
--------------------------------------------------------------------------------
1 | .remark-code-title {
2 | font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
3 | "Courier New", monospace;
4 | font-size: 15.75px;
5 | font-weight: 600;
6 | display: flex;
7 | width: 100%;
8 | padding: 10px 8px;
9 | margin: 0.5em 0 0 0;
10 | border: 1px solid gray;
11 | border-bottom: none;
12 | }
13 |
14 | .chakra-ui-light .remark-code-title {
15 | color: #1a202c;
16 | background-color: #edf2f7;
17 | }
18 |
19 | .chakra-ui-dark .remark-code-title {
20 | color: #e2e8f0;
21 | background-color: #e2e8f029;
22 | }
23 |
24 | pre {
25 | padding: 10px 0;
26 | }
27 |
28 | .remark-code-title + pre {
29 | padding: 0 0 10px 0;
30 | }
31 |
32 | blockquote {
33 | margin: 1em 0;
34 | padding-left: 1em;
35 |
36 | font-weight: 400;
37 | font-style: italic;
38 | border-left-width: 0.25rem;
39 | border-left-color: #ced4da;
40 | quotes: "\201C""\201D""\2018""\2019";
41 | }
42 |
43 | blockquote p:first-of-type:before {
44 | content: open-quote;
45 | }
46 |
47 | blockquote p:last-of-type:after {
48 | content: close-quote;
49 | }
50 |
--------------------------------------------------------------------------------
/config.js:
--------------------------------------------------------------------------------
1 | export const seo = {
2 | title: "Portfolio Boilerplate",
3 | description:
4 | "A Next.js boilerplate for building your portfolio as quick as possible",
5 | canonical: "https://portfolio-boilerplate-nextjs.vercel.app/",
6 | };
7 |
8 | export const data = [
9 | {
10 | title: "Your title",
11 | description:
12 | "Lorem ipsum dolor sit amet consectetur adipisicing elit. Nulla tempora dolorem doloribus repudiandae, possimus quod quas. Ipsum culpa repellat dolorem vero odit iste delectus id, sed iure facere, animi suscipit.",
13 | image: "/code-review-bro.png",
14 | },
15 | {
16 | title: "Your title",
17 | description:
18 | "Lorem ipsum dolor sit amet consectetur adipisicing elit. Nulla tempora dolorem doloribus repudiandae, possimus quod quas. Ipsum culpa repellat dolorem vero odit iste delectus id, sed iure facere, animi suscipit.",
19 | image: "/code-review-bro.png",
20 | },
21 | {
22 | title: "Your title",
23 | description:
24 | "Lorem ipsum dolor sit amet consectetur adipisicing elit. Nulla tempora dolorem doloribus repudiandae, possimus quod quas. Ipsum culpa repellat dolorem vero odit iste delectus id, sed iure facere, animi suscipit.",
25 | image: "/code-review-bro.png",
26 | },
27 | ];
28 |
29 | export const MAX_WIDTH = "1440px";
30 |
--------------------------------------------------------------------------------
/src/data/blog/ssg-ssr.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "When to Use Static Generation v.s. Server-side Rendering"
3 | publishedAt: "2020-01-02"
4 | modifiedAt: "2021-3-13"
5 | author: "author"
6 | summary: "the difference between server-side Rendering and Static Generation"
7 | tags: ["nextjs", "ssr", "optimization", "asynchronous"]
8 | ---
9 |
10 | We recommend using **Static Generation** (with and without data) whenever possible because your page can be built once and served by CDN, which makes it much faster than having a server render the page on every request.
11 |
12 | You can use Static Generation for many types of pages, including:
13 |
14 | - Marketing pages
15 | - Blog posts
16 | - E-commerce product listings
17 | - Help and documentation
18 |
19 | You should ask yourself: "Can I pre-render this page **ahead** of a user's request?" If the answer is yes, then you should choose Static Generation.
20 |
21 | On the other hand, Static Generation is **not** a good idea if you cannot pre-render a page ahead of a user's request. Maybe your page shows frequently updated data, and the page content changes on every request.
22 |
23 | In that case, you can use **Server-Side Rendering**. It will be slower, but the pre-rendered page will always be up-to-date. Or you can skip pre-rendering and use client-side JavaScript to populate data.
24 |
--------------------------------------------------------------------------------
/src/pages/_document.jsx:
--------------------------------------------------------------------------------
1 | import { ColorModeScript } from "@chakra-ui/react";
2 | import Document, { Html, Head, Main, NextScript } from "next/document";
3 |
4 | import colorMode from "@/theme/colorMode";
5 |
6 | class MyDocument extends Document {
7 | render() {
8 | return (
9 |
10 |
11 |
16 |
21 |
27 |
33 |
34 |
35 |
36 |
39 |
40 |
41 |
42 |
43 |
44 | );
45 | }
46 | }
47 |
48 | export default MyDocument;
49 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "portfolio-boilerplate-nextjs",
3 | "version": "0.1.0",
4 | "private": true,
5 | "license": "MIT",
6 | "repository": {
7 | "url": "https://github.com/imadatyatalah/portfolio-boilerplate-nextjs"
8 | },
9 | "scripts": {
10 | "dev": "next dev",
11 | "build": "next build",
12 | "start": "next start",
13 | "postbuild": "next-sitemap --config next-sitemap.js",
14 | "prettier:format": "prettier --write .",
15 | "prettier:check": "prettier --check ."
16 | },
17 | "dependencies": {
18 | "@chakra-ui/icons": "^1.1.5",
19 | "@chakra-ui/react": "^1.8.3",
20 | "@emotion/react": "^11",
21 | "@emotion/styled": "^11",
22 | "@fontsource/poppins": "^4.5.1",
23 | "@mdx-js/loader": "^1.6.22",
24 | "@next/mdx": "^12.0.10",
25 | "dayjs": "^1.10.7",
26 | "framer-motion": "^5",
27 | "fuse.js": "^6.5.3",
28 | "gray-matter": "^4.0.3",
29 | "hamburger-react": "^2.4.1",
30 | "next": "^12.0.10",
31 | "next-mdx-remote": "^2.1.4",
32 | "next-seo": "^4.28.1",
33 | "prism-react-renderer": "^1.3.1",
34 | "react": "17.0.2",
35 | "react-dom": "17.0.2",
36 | "react-icons": "^4.3.1",
37 | "reading-time": "^1.5.0",
38 | "remark-code-titles": "^0.1.2",
39 | "remark-slug": "^7.0.1"
40 | },
41 | "devDependencies": {
42 | "next-sitemap": "^1.6.203",
43 | "prettier": "^2.5.1"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/public/sitemap.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | https://portfolio-boilerplate-nextjs.vercel.appdaily0.72021-12-26T16:13:30.473Z
4 | https://portfolio-boilerplate-nextjs.vercel.app/blogdaily0.72021-12-26T16:13:30.474Z
5 | https://portfolio-boilerplate-nextjs.vercel.app/blog/lorem-ipsumdaily0.72021-12-26T16:13:30.474Z
6 | https://portfolio-boilerplate-nextjs.vercel.app/blog/markdown-examplesdaily0.72021-12-26T16:13:30.474Z
7 | https://portfolio-boilerplate-nextjs.vercel.app/blog/portfolio-boilerplate-nextjs-introducingdaily0.72021-12-26T16:13:30.474Z
8 | https://portfolio-boilerplate-nextjs.vercel.app/blog/pre-renderingdaily0.72021-12-26T16:13:30.474Z
9 | https://portfolio-boilerplate-nextjs.vercel.app/blog/ssg-ssrdaily0.72021-12-26T16:13:30.474Z
10 |
--------------------------------------------------------------------------------
/src/lib/posts.js:
--------------------------------------------------------------------------------
1 | import { join } from "path";
2 | import { readdirSync, readFileSync } from "fs";
3 | import matter from "gray-matter";
4 | import readingTime from "reading-time";
5 | import remarkSlug from "remark-slug";
6 | import renderToString from "next-mdx-remote/render-to-string";
7 |
8 | import MDXComponents from "@/components/MDXComponents";
9 |
10 | const root = process.cwd();
11 |
12 | export async function getFiles(type) {
13 | return readdirSync(join(root, "src/data", type));
14 | }
15 |
16 | export async function getFileBySlug(type, slug) {
17 | const source = slug
18 | ? readFileSync(join(root, "src/data", type, `${slug}.mdx`), "utf8")
19 | : readFileSync(join(root, "src/data", `${type}.mdx`), "utf8");
20 |
21 | const { data, content } = matter(source);
22 | const mdxSource = await renderToString(content, {
23 | components: MDXComponents,
24 | mdxOptions: {
25 | remarkPlugins: [remarkSlug, require("remark-code-titles")],
26 | },
27 | });
28 |
29 | return {
30 | mdxSource,
31 | frontMatter: {
32 | wordCount: content.split(/\s+/gu).length,
33 | readingTime: readingTime(content),
34 | slug: slug || null,
35 | ...data,
36 | },
37 | };
38 | }
39 |
40 | export async function getAllFilesFrontMatter(type) {
41 | const files = readdirSync(join(root, "src/data", type));
42 |
43 | return files.reduce((allPosts, postSlug) => {
44 | const source = readFileSync(join(root, "src/data", type, postSlug), "utf8");
45 | const { data } = matter(source);
46 |
47 | return [
48 | {
49 | ...data,
50 | slug: postSlug.replace(".mdx", ""),
51 | },
52 | ...allPosts,
53 | ];
54 | }, []);
55 | }
56 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | ```
12 |
13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
14 |
15 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
16 |
17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
18 |
19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
20 |
21 | ## Learn More
22 |
23 | To learn more about Next.js, take a look at the following resources:
24 |
25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
27 |
28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
29 |
30 | ## Deploy on Vercel
31 |
32 | [](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fimadatyatalah%2Fnextjs-chakra-ui-portfolio-template&project-name=nextjs-chakra-ui-portfolio-template&repo-name=nextjs-chakra-ui-portfolio-template&demo-description=A+Next.js+template+for+building+your+portfolio+as+quick+as+possible+with+a+nice+design.&demo-url=https%3A%2F%2Fportfolio-boilerplate-nextjs.vercel.app%2F)
33 |
--------------------------------------------------------------------------------
/src/components/MDXComponents/codeBlock.jsx:
--------------------------------------------------------------------------------
1 | import { Box, Button, useClipboard, useColorMode } from "@chakra-ui/react";
2 | import Highlight, { defaultProps } from "prism-react-renderer";
3 | import lightTheme from "prism-react-renderer/themes/nightOwlLight";
4 | import darkTheme from "prism-react-renderer/themes/nightOwl";
5 |
6 | import styles from "./styles/codeBlock.module.css";
7 |
8 | const CodeBlock = ({ children, className }) => {
9 | const { colorMode } = useColorMode();
10 | const { hasCopied, onCopy } = useClipboard(children);
11 |
12 | const language = className.replace(/language-/, "");
13 |
14 | return (
15 |
16 |
27 |
28 |
34 | {({ className, style, tokens, getLineProps, getTokenProps }) => (
35 |
39 | {tokens.map((line, i) => (
40 |
41 | {i + 1}
42 |
43 |
44 | {line.map((token, key) => (
45 |
46 | ))}
47 |
48 |
49 | ))}
50 |
51 | )}
52 |
53 |
54 | );
55 | };
56 |
57 | export default CodeBlock;
58 |
--------------------------------------------------------------------------------
/src/components/header/navbar.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { Box, chakra, useColorModeValue } from "@chakra-ui/react";
3 | import NextLink from "next/link";
4 |
5 | import HamburgerMenu from "../UI/hamburgerMenu";
6 | import ColorModeToggle from "../UI/colorModeToggle";
7 |
8 | const Navbar = () => {
9 | const [isOpen, setIsOpen] = useState(false);
10 |
11 | const bg = useColorModeValue("gray.200", "gray.300");
12 | const color = useColorModeValue("black", "white");
13 |
14 | const closeMenu = () => {
15 | setIsOpen(false);
16 | };
17 |
18 | return (
19 |
26 |
27 |
45 |
50 |
51 | Home
52 |
53 |
54 |
55 |
60 |
61 | Blog
62 |
63 |
64 |
65 |
66 |
67 |
68 | );
69 | };
70 |
71 | export default Navbar;
72 |
--------------------------------------------------------------------------------
/src/data/blog/markdown-examples.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Markdown Examples"
3 | publishedAt: "2021-04-29"
4 | modifiedAt: "2021-06-17"
5 | author: "Imad Atyat-Alah"
6 | summary: "Markdown Examples"
7 | tags: ["nextjs", "javascript", "markdown"]
8 | ---
9 |
10 | ## Images
11 |
12 | ### With `
` tag
13 |
14 |
21 |
22 | ### With Next.js `` component
23 |
24 |
33 |
34 | ## Headings
35 |
36 | ## h2
37 |
38 | ### h3
39 |
40 | #### h4
41 |
42 | ##### h5
43 |
44 | ###### h6
45 |
46 | ## Text
47 |
48 | **this text is bold**
49 |
50 | _this text is italic_
51 |
52 | ~~this text is strikethrough~~
53 |
54 | ## Blockquotes
55 |
56 | > Somewhere, something incredible is waiting to be known.
57 |
58 | ## Lists
59 |
60 | ### Unordered
61 |
62 | - Lorem ipsum dolor sit amet
63 | - Lorem ipsum dolor sit amet
64 | - Lorem ipsum dolor sit amet
65 |
66 | ### ordered
67 |
68 | 1. Lorem ipsum dolor sit amet
69 | 2. Lorem ipsum dolor sit amet
70 | 3. Lorem ipsum dolor sit amet
71 |
72 | ## Code
73 |
74 | Inline `code`
75 |
76 | ### JavaScriptReact code
77 |
78 | ```jsx:src/pages/_app.jsx
79 | import { ChakraProvider } from "@chakra-ui/react"
80 | import { DefaultSeo } from "next-seo"
81 |
82 | import theme from "@/theme/index"
83 | import Layout from "@/layouts/global"
84 | import SEO from "next-seo.config"
85 |
86 | import "@fontsource/poppins/latin-400.css"
87 | import "@fontsource/poppins/latin-500.css"
88 | import "@fontsource/poppins/latin-600.css"
89 | import "@fontsource/poppins/latin-700.css"
90 | import "@/styles/index.css"
91 |
92 | const MyApp = ({ Component, pageProps }) => (
93 | <>
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | >
102 | )
103 |
104 | export default MyApp
105 | ```
106 |
--------------------------------------------------------------------------------
/src/components/blogPost.jsx:
--------------------------------------------------------------------------------
1 | import { Box, Heading, Text, useColorModeValue } from "@chakra-ui/react";
2 | import { useRouter } from "next/router";
3 | import NextLink from "next/link";
4 | import dayjs from "dayjs";
5 |
6 | import { tagColor } from "./UI/tagColor";
7 | import TagComponent from "./UI/tag";
8 |
9 | const BlogPost = ({ posts }) => {
10 | const router = useRouter();
11 |
12 | const summaryColor = useColorModeValue("gray.600", "gray.300");
13 | const dateColor = useColorModeValue("gray.500", "gray.400");
14 | const yearColor = useColorModeValue("telegram.500", "telegram.400");
15 |
16 | let year = 0;
17 |
18 | return (
19 | <>
20 | {posts.map((post) => {
21 | const { slug, title, summary, tags, publishedAt } = post;
22 |
23 | const thisYear = publishedAt.substring(0, 4);
24 |
25 | const YearComponent = thisYear !== year && (
26 |
27 | {thisYear}
28 |
29 | );
30 |
31 | year = thisYear;
32 |
33 | return (
34 |
35 | {YearComponent}
36 |
37 |
38 |
39 | {title}
40 |
41 |
42 |
43 |
44 | {summary}
45 |
46 |
47 | {tags.map((tag) => {
48 | const color = tagColor[tag];
49 |
50 | return (
51 |
54 | router.push({
55 | pathname: "/blog/",
56 | query: { tag },
57 | })
58 | }
59 | key={tag}
60 | >
61 | {tag}
62 |
63 | );
64 | })}
65 |
66 |
67 | {dayjs(publishedAt).format("MMMM DD, YYYY")}
68 |
69 |
70 | );
71 | })}
72 | >
73 | );
74 | };
75 |
76 | export default BlogPost;
77 |
--------------------------------------------------------------------------------
/src/data/blog/lorem-ipsum.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Lorem Ipsum"
3 | publishedAt: "2021-2-28"
4 | modifiedAt: "2021-3-13"
5 | author: "author"
6 | summary: "Lorem Ipsum is simply dummy text of the printing and typesetting industry..."
7 | tags: ["css", "html"]
8 | ---
9 |
10 | ## What is Lorem Ipsum?
11 |
12 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
13 |
14 | ## Why do we use it?
15 |
16 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
17 |
18 | ## Where does it come from?
19 |
20 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
21 |
22 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
23 |
24 | ## Where can I get some?
25 |
26 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
27 |
--------------------------------------------------------------------------------
/src/components/footer.jsx:
--------------------------------------------------------------------------------
1 | import { Box, Button, Icon, Link as ChakraLink, Text } from "@chakra-ui/react";
2 | import { FaGithub, FaLinkedin, FaInstagram, FaCode } from "react-icons/fa";
3 |
4 | import { MAX_WIDTH } from "config";
5 |
6 | // Fell free to add your social media accounts!
7 | const socialAccounts = [
8 | { icon: FaGithub, path: "https://github.com/", title: "Github" },
9 | { icon: FaLinkedin, path: "https://www.linkedin.com/", title: "Linkedin" },
10 | { icon: FaInstagram, path: "https://www.instagram.com/", title: "Instagram" },
11 | ];
12 |
13 | const Footer = () => {
14 | return (
15 |
16 |
27 |
28 | {socialAccounts.map((item, index) => (
29 |
37 |
40 |
41 | ))}
42 |
43 |
44 |
45 | Built with{" "}
46 |
47 | ❤️
48 |
49 | ,{" "}
50 |
56 | Next.js
57 |
58 | ,{" "}
59 |
65 | Chakra UI
66 |
67 | , Hosted in{" "}
68 |
74 | Vercel.
75 |
76 |
77 |
78 |
79 |
84 |
87 |
88 |
89 |
90 |
91 | );
92 | };
93 |
94 | export default Footer;
95 |
--------------------------------------------------------------------------------
/src/pages/blog.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useCallback } from "react";
2 | import { Alert, AlertIcon, Box, Flex, Input } from "@chakra-ui/react";
3 | import { useRouter } from "next/router";
4 | import { NextSeo } from "next-seo";
5 | import Fuse from "fuse.js";
6 |
7 | import { getAllFilesFrontMatter } from "@/lib/posts";
8 | import { tagColor } from "@/components/UI/tagColor";
9 | import { seo } from "config";
10 | import TagComponent from "@/components/UI/tag";
11 | import BlogPost from "@/components/blogPost";
12 |
13 | const options = {
14 | includeScore: true,
15 | threshold: 0.3,
16 | ignoreLocation: true,
17 | keys: ["title"],
18 | };
19 |
20 | const Blog = ({ posts }) => {
21 | const router = useRouter();
22 |
23 | const fuse = new Fuse(posts, options);
24 |
25 | const [blogPost, setBlogPost] = useState(posts);
26 | const [searchValue, setSearchValue] = useState("");
27 |
28 | const filteredPosts = (tag) => {
29 | const blogResults = posts.filter((post) => post.tags.includes(tag));
30 | setBlogPost(blogResults);
31 | };
32 |
33 | const updateSearch = () => {
34 | const results = fuse.search(searchValue);
35 | const blogResults = results.map((res) => res.item);
36 | setBlogPost(blogResults);
37 | };
38 |
39 | const delayedSearch = useCallback(updateSearch, [searchValue]);
40 |
41 | useEffect(() => {
42 | if (searchValue.length === 0) {
43 | return setBlogPost(posts);
44 | }
45 | delayedSearch();
46 | }, [delayedSearch]);
47 |
48 | useEffect(() => {
49 | if (router.query?.tag !== undefined) {
50 | filteredPosts(router.query?.tag);
51 | }
52 | }, [router]);
53 |
54 | const title = "Blog";
55 | const description = seo.description;
56 | const url = `${seo.canonical}blog`;
57 |
58 | return (
59 | <>
60 |
70 |
71 |
77 |
78 | {
80 | setSearchValue(e.target.value);
81 | }}
82 | value={searchValue}
83 | variant="outline"
84 | placeholder="Search..."
85 | maxWidth="400px"
86 | />
87 |
88 |
89 |
96 | {Object.keys(tagColor).map((tag, index) => {
97 | const color = tagColor[tag];
98 |
99 | return (
100 |
101 | filteredPosts(tag)}>
102 | {tag}
103 |
104 |
105 | );
106 | })}
107 |
108 |
109 | {blogPost.length > 0 ? (
110 |
111 | ) : (
112 |
121 |
122 | No blog post has been found!
123 |
124 | )}
125 |
126 | >
127 | );
128 | };
129 |
130 | export async function getStaticProps() {
131 | const data = await getAllFilesFrontMatter("blog");
132 | const posts = data.sort(
133 | (a, b) => Number(new Date(b.publishedAt)) - Number(new Date(a.publishedAt))
134 | );
135 |
136 | return { props: { posts } };
137 | }
138 |
139 | export default Blog;
140 |
--------------------------------------------------------------------------------
/src/pages/index.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Box,
3 | Button,
4 | Heading,
5 | Text,
6 | useColorModeValue,
7 | } from "@chakra-ui/react";
8 | import { NextSeo } from "next-seo";
9 | import NextImage from "next/image";
10 |
11 | import { seo, data } from "config";
12 |
13 | const Home = () => {
14 | const color = useColorModeValue("telegram.500", "telegram.400");
15 |
16 | const isOdd = (num) => num % 2;
17 |
18 | const title = "Home";
19 | const description = seo.description;
20 |
21 | return (
22 | <>
23 |
40 |
41 |
49 |
58 |
59 |
60 | Hi, I'm John Doe{" "}
61 |
62 | 👋🏻
63 |
64 |
65 |
66 |
67 | Building
68 | {" "}
69 | digital products, Brands, And experience.
70 |
71 |
72 | A{" "}
73 |
74 | web designer
75 | {" "}
76 | and{" "}
77 |
78 | front-end web developer
79 | {" "}
80 | based in the US, I specialize in UI/UX design, Responsive web
81 | design, And accessibility.
82 |
83 |
91 |
92 |
93 |
94 |
102 | {data.map((item, index) => (
103 |
110 |
116 |
124 |
125 |
126 |
127 | {item.title}
128 | {item.description}
129 |
130 |
131 | ))}
132 |
133 | >
134 | );
135 | };
136 |
137 | export default Home;
138 |
--------------------------------------------------------------------------------
/src/pages/blog/[slug].jsx:
--------------------------------------------------------------------------------
1 | import { NextSeo } from "next-seo";
2 | import { useRouter } from "next/router";
3 | import {
4 | Box,
5 | Flex,
6 | Heading,
7 | Text,
8 | Link as ChakraLink,
9 | useColorModeValue,
10 | Icon,
11 | } from "@chakra-ui/react";
12 | import { MDXProvider } from "@mdx-js/react";
13 | import { MdEdit } from "react-icons/md";
14 | import dayjs from "dayjs";
15 | import hydrate from "next-mdx-remote/hydrate";
16 |
17 | import { getFiles, getFileBySlug } from "@/lib/posts";
18 | import { seo } from "config";
19 | import { tagColor } from "@/components/UI/tagColor";
20 | import MDXComponents from "@/components/MDXComponents";
21 | import TagComponent from "@/components/UI/tag";
22 |
23 | const BlogPost = ({ mdxSource, frontMatter }) => {
24 | const { push } = useRouter();
25 |
26 | const color = useColorModeValue("gray.700", "gray.400");
27 |
28 | const content = hydrate(mdxSource, {
29 | components: MDXComponents,
30 | });
31 |
32 | const title = frontMatter.title;
33 | const description = frontMatter.summary;
34 | const url = `${seo.canonical}blog/${frontMatter.slug}`;
35 |
36 | return (
37 | <>
38 | tag),
51 | },
52 | }}
53 | />
54 |
55 |
56 |
62 |
63 |
64 | {frontMatter.title}
65 |
66 |
67 |
68 |
69 | {frontMatter.author} /{" "}
70 | {dayjs(frontMatter.publishedAt).format("MMMM DD, YYYY")} /{" "}
71 | {frontMatter.readingTime.text}
72 |
73 |
74 | {frontMatter.tags.map((tag) => {
75 | const color = tagColor[tag];
76 |
77 | return (
78 |
81 | push({
82 | pathname: "/blog/",
83 | query: { tag },
84 | })
85 | }
86 | key={tag}
87 | >
88 | {tag}
89 |
90 | );
91 | })}
92 |
93 |
94 |
95 |
96 |
97 | {content}
98 |
99 |
100 |
107 |
108 | Edit this page on github.
109 |
110 |
111 |
112 |
113 |
114 | >
115 | );
116 | };
117 |
118 | export const getStaticPaths = async () => {
119 | const posts = await getFiles("blog");
120 |
121 | return {
122 | paths: posts.map((post) => ({
123 | params: {
124 | slug: post.replace(/\.mdx/, ""),
125 | },
126 | })),
127 |
128 | fallback: false,
129 | };
130 | };
131 |
132 | export const getStaticProps = async ({ params }) => {
133 | const post = await getFileBySlug("blog", params.slug);
134 |
135 | return { props: post };
136 | };
137 |
138 | export default BlogPost;
139 |
--------------------------------------------------------------------------------
/public/bighead.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------