Skip to content

Commit

Permalink
Migrate to Next.js 14 app directory (#156)
Browse files Browse the repository at this point in the history
* chore(deps): bump ini from 1.3.5 to 1.3.8

Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.8.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](npm/ini@v1.3.5...v1.3.8)

Signed-off-by: dependabot[bot] <[email protected]>

* chore(deps): bump y18n from 4.0.0 to 4.0.3

Bumps [y18n](https://github.com/yargs/y18n) from 4.0.0 to 4.0.3.
- [Release notes](https://github.com/yargs/y18n/releases)
- [Changelog](https://github.com/yargs/y18n/blob/y18n-v4.0.3/CHANGELOG.md)
- [Commits](yargs/y18n@v4.0.0...y18n-v4.0.3)

Signed-off-by: dependabot[bot] <[email protected]>

* chore(deps): bump ssri from 6.0.1 to 6.0.2

Bumps [ssri](https://github.com/npm/ssri) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/npm/ssri/releases)
- [Changelog](https://github.com/npm/ssri/blob/v6.0.2/CHANGELOG.md)
- [Commits](npm/ssri@v6.0.1...v6.0.2)

Signed-off-by: dependabot[bot] <[email protected]>

* chore(deps): bump elliptic from 6.5.3 to 6.5.4

Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.3 to 6.5.4.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](indutny/elliptic@v6.5.3...v6.5.4)

Signed-off-by: dependabot[bot] <[email protected]>

* Add renovate.json

* fix(deps): pin dependencies

* chore(deps): update dependency @types/node to v14.14.43

* Add pinned dependencies

* Add pins to dependencies

* feat: add plausible analytics

* fix: use url for post id instead of randomly generated id. fixes #10

* chore: remove plausible analysis ��‍♂️

* fix: use url for post id instead of randomly generated id. fixed #10

* Fix typo

* Change react-tilt to react-parallax-tilt

* Revert to double quotes

* Update options

* chore: upgrade packages

* refactor: remove <a> tags from <Link>

* fix: add eslint-config-next and fix linting issues

* fix: breaking build

* chore: install tailwindcss deps

* refactor: remove emotion styled from codebase

* chore: remove emotion packages

* refactor: bring in global css to tailwind base

* chore: migrate articles to app router

* chore: move all pages to app router

* refactor: mobile navigation

* refactor: support tags page

* feat: statically generate all content

* chore: add alias for component imports

* refactor: add alias for utils, various refactors

* Switch Emotion to Tailwind CSS

* chore: various layout changes and refactor

* chore: more ui refactors

* feat: various ui improvements

* feat: switch to feed for rss

* feat: generate rss for articles at build time

* fix: perfect mobile navigation

* fix: correct dark mode

* chore: upgrade to next 14

* chore: implement seo and social images

* fix: social previews

* chore: various refactors, add dummy content

* chore: update documentation

* Switch Emotion to Tailwind CSS

* chore: update documentation

* Fix merge conflicts

* Fix merge conflicts

* Update dependencies

* Update config, package.json, remove css duplication

* Add Prettier changes

* Update Readme

* Update Playwright test

* Add highlight.js

* Fix imports

* Fix imports, Prettier changes

* Fix postcss for Tailwind

* Refactor import statements and variable names

* Refactor key values in components

* Fix TypeScript errors

* Add data-test-ids

* Remove .eslintrc.json and fix minor TS errors

* Add line break

* Remove empty line

* Remove empty line

* Refactor test, remove data-test-id

* Update lockfile

---------

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Renovate Bot <[email protected]>
Co-authored-by: José Fernando Höwer Barbosa <[email protected]>
Co-authored-by: Josehower <[email protected]>
Co-authored-by: Victor Ofoegbu <[email protected]>
Co-authored-by: Karl Horky <[email protected]>
  • Loading branch information
7 people authored Jan 5, 2024
1 parent 1c511f2 commit a1af9f5
Show file tree
Hide file tree
Showing 123 changed files with 3,116 additions and 3,375 deletions.
Binary file added .github/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ yarn-error.log*
# vercel
.vercel

public/rss.xml

.eslintcache
*.tsbuildinfo

Expand Down
48 changes: 11 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
![Next Starter Peacock](./screenshot.png)
![Next Starter Peacock](./.github/screenshot.png)

# 🦚 Next Starter Peacock

Peacock is a NextJS portfolio Starter for software engineers and designers. Showcase your awesome work and build personal sites you're proud of.

## Features

- Styled with EmotionJS💅🏾
- Written in TypeScript
- Blog, Notes and Work content types 🖊
- Styled with Tailwind CSS
- Written in TypeScript & Next 14 (RSC)
- Blog, Notes and Work content types

## Getting Started

Expand All @@ -20,46 +20,19 @@ Peacock is a NextJS portfolio Starter for software engineers and designers. Show
git clone https://github.com/<your-username>/next-starter-peacock.git
```

3. Install the dependencies for `canvas` (see [Automattic/node-canvas#1065](https://github.com/Automattic/node-canvas/issues/1065)).

on macOS:

```bash
brew install pkg-config pixman cairo pango
```

on Windows:

```bash
choco install -y python visualstudio2017-workload-vctools gtk-runtime libjpeg-turbo gtk-runtime
npm config set msvs_version 2017
```

> NOTE: In case this command doesn't work, You may need the `cairo` library, which is bundled in GTK. Download the GTK 2 bundle for [Win32](http://ftp.gnome.org/pub/GNOME/binaries/win32/gtk+/2.24/gtk+-bundle_2.24.10-20120208_win32.zip) or [Win64](http://ftp.gnome.org/pub/GNOME/binaries/win64/gtk+/2.22/gtk+-bundle_2.22.1-20101229_win64.zip). Unzip the contents in `C:\GTK`
>
> You may also find more info about `canvas` windows manual installation in the [Wiki](https://github.com/Automattic/node-canvas/wiki/Installation:-Windows).
4. Jump into the directory and Install dependencies
3. Jump into the directory and Install dependencies

```bash
cd next-starter-peacock && yarn

or

cd next-starter-peacock && npm install
cd next-starter-peacock && pnpm install
```

5. Start the dev server
4. Start the dev server

```bash
yarn dev

or

npm run dev
pnpm dev
```

6. Find `config/index.json`, change `name` and `title` to your name and title. Save and open [http://localhost:3000](http://localhost:3000) with your browser 💥💥💥. See the results 😊.
5. Find `config/index.json`, change `name` and `title` to your name and title. Save and open [http://localhost:3000](http://localhost:3000) with your browser 💥💥💥. See the results 😊.

---

Expand Down Expand Up @@ -98,6 +71,7 @@ The fastest way to get an answer to your question is to reach out via [Twitter](
- [x] Image optimization (Fixed image heights to avoid layout janks)
- [x] RSS Feed
- [ ] Release V1
- [ ] Add page transitions with `react-spring`
- [x] Add page transitions with `framer-motion`
- [ ] Add mdx support
- [x] Code syntax highlighting
- [ ] og image generation with vercel/og
18 changes: 18 additions & 0 deletions app/[contentType]/[slug]/content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use client';
import hljs from 'highlight.js/lib/core';
import javascript from 'highlight.js/lib/languages/javascript';
import { useEffect } from 'react';

export default function Content({ html }: { html: string }) {
useEffect(() => {
hljs.registerLanguage('javascript', javascript);
hljs.highlightAll();
}, []);

return (
<div
className="content-body mb-8"
dangerouslySetInnerHTML={{ __html: html }}
/>
);
}
189 changes: 189 additions & 0 deletions app/[contentType]/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import { Metadata } from 'next';
import Image from 'next/image';
import { notFound } from 'next/navigation';
import { Chips, Container } from '../../../components';
import BackButton from '../../../components/back-button';
import { author, site } from '../../../config/index.json';
import {
getContentData,
getContentList,
getContentTypes,
IContentType,
} from '../../../utils/content';
import { contentTypesMap } from '../../../utils/content-types';
import Content from './content';

export async function generateMetadata({
params,
}: {
params: Params;
}): Promise<Metadata> {
const { title, previewImage, description } = await getContentData(
params.slug,
params.contentType,
);
return {
title: `${title} | ${site.siteTitle}`,
description: description ?? site.siteDescription,
openGraph: {
title: `${title} | ${site.siteName}`,
description: description ?? site.siteDescription,
url: site.siteUrl,
images: previewImage ?? site.siteImage,
siteName: site.siteName,
},
twitter: {
card: 'summary_large_image',
creator: author.twitterHandle,
images: previewImage ?? site.siteImage,
},
};
}

/**
* statically generate all content pages
*/
export function generateStaticParams() {
const contentTypes = getContentTypes();

return contentTypes.flatMap((contentType) => {
const contentList = getContentList(contentType);
return contentList.map(({ slug }) => {
return {
contentType,
slug,
};
});
});
}

/**
* Renders articles markdown posts
*/

async function fetchContentData(slug: string, contentType: IContentType) {
return await getContentData(slug, contentType);
}

type Params = {
slug: string;
contentType: IContentType;
};

export default async function ContentPage({ params }: { params: Params }) {
const { slug, contentType } = params;

if (!contentTypesMap.has(contentType)) {
return notFound();
}

const content = await fetchContentData(slug, contentType);
if (content.draft) return notFound();

if (contentType === 'works') return <WorkPage work={content} />;

return (
<Container width="narrow">
<header>
<section className="pt-16">
<h1 className="my-0 font-bold font-display mb-2 text-2xl/normal md:text-4xl max-w-xl">
{content.title}
</h1>
<time className="block text-accent-4 mb-8">
{content.date.toString()}
</time>
{content.previewImage && (
<Image
className="pb-8 block object-cover"
src={content.previewImage}
height={550}
width={1200}
alt=""
/>
)}
</section>
</header>

<Content html={content.contentHtml} />
{content.tags && <Chips items={content.tags} />}
</Container>
);
}

function WorkPage({ work }: { work: IContentData }) {
return (
<Container className="flex flex-col lg:flex-row gap-4 pt-12">
<section className="w-full lg:w-1/3 border-r border-accent-8 p-2 pr-8">
<div className="mb-8 flex flex-col items-start gap-5">
<BackButton />
<h1 className="text-4xl font-bold font-display text-accent-3">
{work.title}
</h1>
<p className="text-accent-4">{work.description}</p>
<button>Se Demo</button>
</div>

<ul>
<TechStack techStack={work.techStack ?? []} />
<MetadataListItem item="Date" value={work.date.toString()} />
{Boolean(work.problem) && (
<MetadataListItem item="Problem" value={work.problem ?? ''} />
)}
</ul>
</section>

<section className="w-full lg:w-2/3 p-2">
<Image
src={work.previewImage ?? ''}
height={1000}
width={1000}
alt=""
className="mb-4"
/>
<Content html={work.contentHtml} />
</section>
</Container>
);
}

function MetadataListItem({ item, value }: { item: string; value: string }) {
return (
<li className="list-none flex gap-4 border-b border-accent-8 py-3 text-sm">
<span className="text-accent-4 w-1/3">{item}</span>
<span className="w-2/3 text-accent-2">{value}</span>
</li>
);
}

function TechStack({ techStack }: { techStack: string[] }) {
if (!techStack.length) return null;
return (
<li className="list-none flex gap-4 border-b border-accent-8 py-3 text-sm">
<span className="text-accent-4 w-1/3 flex-shrink-0">Tech Stack</span>

<ul className="flex flex-wrap gap-2 flex-grow-0">
{techStack.map((tech) => (
<li
key={`techStack-${tech}`}
className="select-none bg-accent-8 text-accent-2 px-2 py-1 rounded-md"
>
{tech}
</li>
))}
</ul>
</li>
);
}

export interface IContentData {
id: string;
contentHtml: string;
date: Date;
title: string;
previewImage?: string;
description?: string;
tags?: string[];
category?: string;
problem?: string;
techStack?: string[];
}
60 changes: 60 additions & 0 deletions app/[contentType]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { Container, ContentList } from '../../components';
import { site } from '../../config/index.json';
import {
getContentList,
getContentTypes,
IContentType,
} from '../../utils/content';
import { contentTypesMap } from '../../utils/content-types';
import { generateRSS } from '../../utils/rss';

type Params = {
contentType: IContentType;
};

/** generate list page metadata */
export function generateMetadata({ params }: { params: Params }): Metadata {
const contentType = contentTypesMap.get(params.contentType);
return {
title: `${contentType.title} | ${site.siteTitle}`,
description: contentType.description,
};
}

export function generateStaticParams() {
generateRSS();

const contentTypes = getContentTypes();
return Array.from(contentTypes).map((contentType) => ({
contentType,
}));
}

/**
* Index page `/index`
*/
export default function ContentListPage({ params }: { params: Params }) {
const contentType = params.contentType;

// redirect to 404 with wrong contentType
if (!contentTypesMap.has(contentType)) {
return notFound();
}

const content = getContentList(contentType);
const isNotes = contentType.toLowerCase() === 'notes';
const { title, path, description } = contentTypesMap.get(contentType);

return (
<Container width={isNotes ? 'narrow' : 'default'}>
<section className="flex flex-col py-20 gap-2 max-w-2xl">
<h1 className="text-4xl font-bold font-display">{title}</h1>
<p className="text-accent-4 text-lg">{description}</p>
</section>

<ContentList basePath={path} items={content} contentType={contentType} />
</Container>
);
}
36 changes: 36 additions & 0 deletions app/[contentType]/tags/[tag]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Container, ContentList } from '../../../../components';
import { getContentWithTag, IContentType } from '../../../../utils/content';
import { contentTypesMap } from '../../../../utils/content-types';

// TODO: I need to rethink the tags page. I don't think I want to have a page for each tag.
// /notes/tags/programming & /articles/tags/programming. Instead I want to have a single page for all tags
// /tags/programming. I think this will be easier to manage and will be more intuitive for readers.

/**
* Index page `/index`
*/
export default function ContentListPage({
params,
}: {
params: {
contentType: IContentType;
tag: IContentType;
};
}) {
const contentType = params.contentType;
const tag = params.tag;

const content = getContentWithTag(tag, contentType);
const isNotes = contentType.toLowerCase() === 'notes';
const { title, path, description } = contentTypesMap.get(contentType);

return (
<Container width={isNotes ? 'narrow' : 'default'}>
<section className="flex flex-col py-20 gap-2 max-w-2xl">
<h1 className="text-4xl font-bold font-display">{title} Tags</h1>
<p className="text-accent-4 text-lg">{description}</p>
</section>
<ContentList contentType={contentType} items={content} basePath={path} />
</Container>
);
}
Loading

0 comments on commit a1af9f5

Please sign in to comment.