Skip to content

Commit

Permalink
feat: Nextfolio NextJS template
Browse files Browse the repository at this point in the history
  • Loading branch information
waltergalvao committed Nov 12, 2024
1 parent 1114482 commit 60fde63
Show file tree
Hide file tree
Showing 48 changed files with 6,532 additions and 2 deletions.
37 changes: 37 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem
.vscode

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files
.env

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
4 changes: 2 additions & 2 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2024 Currents.dev
Copyright © 2024 Nextfolio

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand All @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Nextfolio

A clean, fast, and lightweight portfolio template built with [Next.js](https://nextjs.org/), [Vercel](https://vercel.com/), and [Tailwind CSS](https://tailwindcss.com/) for optimal performance.

Deploy your Nextfolio site with Vercel in minutes.

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2F1msirius%2FNextfolio)

## Technologies Used

- Framework: [Next.js](https://nextjs.org/)
- Typography: [Vercel Geist Font](https://vercel.com/font)
- Styling: [Tailwind CSS](https://tailwindcss.com/)
- Analytics: [Vercel Web Analytics](https://vercel.com/docs/speed-insights) and [Speed Insights](https://vercel.com/docs/speed-insights)
- Deployment: [Vercel](https://vercel.com/)

## Features

- **[MDX](https://mdxjs.com/) Support**: Use Markdown with JSX components for blog posts.
- **Light and Dark Mode Toggle**: Switch between themes for better readability.
- **Dynamic [OG Images](https://vercel.com/docs/functions/og-image-generation)**: Auto-generate Open Graph images for sharing.
- **SEO Optimization**: Enhance search visibility with sitemap, robots.txt, and JSON-LD schema.
- **Dynamic Feed Generation**: Automatic dynamic [RSS](https://nextfolio-template.vercel.app/rss.xml), [Atom](https://nextfolio-template.vercel.app/atom.xml), and [JSON](https://nextfolio-template.vercel.app/feed.json) feeds.
- **[KaTeX](https://katex.org/) Integration**: Render mathematical expressions smoothly.
- **Performance Tracking**: Monitor web performance with [Vercel Web Analytics](https://vercel.com/docs/speed-insights) and [Speed Insights](https://vercel.com/docs/speed-insights).
- **Interactive Embeds**: Easily embed interactive tweets and YouTube videos.
- **Captions**: Add descriptive captions to photos, tweets, and videos.
- **Image Grid**: Easily showcase image galleries or photos.

## Installation

Nextfolio uses [pnpm](https://pnpm.io/installation) for dependency management, so ensure it is installed on your system.

Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [pnpm](https://pnpm.io/installation) to bootstrap the example:

```
pnpm create next-app --example https://github.com/1msirius/Nextfolio my-portfolio
```

Start the development server:

```
pnpm dev
```

The server will be running at [http://localhost:3000](http://localhost:3000).

## Configuration

1. Update the site metadata and social links in `app/config.ts` to set up SEO, feeds, social links, and Open Graph settings.
2. Update your routes in `app/sitemap.ts` for SEO optimization.
3. Update your blog posts in the `/content` folder.

For more information about configuration, follow the instructions in the [Getting Started](https://nextfolio-template.vercel.app/blog/getting-started#configuration) post.

## Contributing

Contributions are welcome! To get involved, just push your code to the repo. Whether you're enhancing existing features or adding new ones, your efforts are greatly appreciated!

## Licence

Nextfolio is open-source and released under the MIT License.
101 changes: 101 additions & 0 deletions app/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { CustomMDX } from "app/components/mdx";
import { formatDate, getBlogPosts } from "app/lib/posts";
import { metaData } from "app/config";

export async function generateStaticParams() {
let posts = getBlogPosts();

return posts.map((post) => ({
slug: post.slug,
}));
}

export async function generateMetadata({
params,
}): Promise<Metadata | undefined> {
let post = getBlogPosts().find((post) => post.slug === params.slug);
if (!post) {
return;
}

let {
title,
publishedAt: publishedTime,
summary: description,
image,
} = post.metadata;
let ogImage = image
? image
: `${metaData.baseUrl}/og?title=${encodeURIComponent(title)}`;

return {
title,
description,
openGraph: {
title,
description,
type: "article",
publishedTime,
url: `${metaData.baseUrl}/blog/${post.slug}`,
images: [
{
url: ogImage,
},
],
},
twitter: {
card: "summary_large_image",
title,
description,
images: [ogImage],
},
};
}

export default function Blog({ params }) {
let post = getBlogPosts().find((post) => post.slug === params.slug);

if (!post) {
notFound();
}

return (
<section>
<script
type="application/ld+json"
suppressHydrationWarning
dangerouslySetInnerHTML={{
__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "BlogPosting",
headline: post.metadata.title,
datePublished: post.metadata.publishedAt,
dateModified: post.metadata.publishedAt,
description: post.metadata.summary,
image: post.metadata.image
? `${metaData.baseUrl}${post.metadata.image}`
: `/og?title=${encodeURIComponent(post.metadata.title)}`,
url: `${metaData.baseUrl}/blog/${post.slug}`,
author: {
"@type": "Person",
name: metaData.name,
},
}),
}}
/>
<h1 className="title mb-3 font-medium text-2xl tracking-tight">
{post.metadata.title}
</h1>
<div className="flex justify-between items-center mt-2 mb-8 text-medium">
<p className="text-sm text-neutral-600 dark:text-neutral-400">
{formatDate(post.metadata.publishedAt)}
</p>
</div>
<article className="prose prose-quoteless prose-neutral dark:prose-invert">
<CustomMDX source={post.content} />
</article>
</section>
);
}
45 changes: 45 additions & 0 deletions app/blog/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import Link from "next/link";
import { formatDate, getBlogPosts } from "app/lib/posts";

export const metadata = {
title: "Blog",
description: "Nextfolio Blog",
};

export default function BlogPosts() {
let allBlogs = getBlogPosts();

return (
<section>
<h1 className="mb-8 text-2xl font-medium tracking-tight">Our Blog</h1>
<div>
{allBlogs
.sort((a, b) => {
if (
new Date(a.metadata.publishedAt) >
new Date(b.metadata.publishedAt)
) {
return -1;
}
return 1;
})
.map((post) => (
<Link
key={post.slug}
className="flex flex-col space-y-1 mb-4 transition-opacity duration-200 hover:opacity-80"
href={`/blog/${post.slug}`}
>
<div className="w-full flex flex-col sm:flex-row justify-between items-start sm:items-center space-y-1 sm:space-y-0 sm:space-x-2">
<p className="text-black dark:text-white tracking-tight">
{post.metadata.title}
</p>
<p className="text-neutral-600 dark:text-neutral-400 tabular-nums text-sm">
{formatDate(post.metadata.publishedAt, false)}
</p>
</div>
</Link>
))}
</div>
</section>
);
}
12 changes: 12 additions & 0 deletions app/components/caption.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Balancer from "react-wrap-balancer";
import type { ReactNode } from "react";

export function CaptionComponent({ children }: { children: ReactNode }) {
return (
<span className="block w-full text-xs my-3 font-mono text-gray-500 text-center leading-normal">
<Balancer>
<span className="[&>a]:post-link">{children}</span>
</Balancer>
</span>
);
}
62 changes: 62 additions & 0 deletions app/components/footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"use client";

import React from "react";
import {
FaXTwitter,
FaGithub,
FaInstagram,
FaRss,
FaLinkedinIn,
} from "react-icons/fa6";
import { TbMailFilled } from "react-icons/tb";
import { metaData, socialLinks } from "app/config";

const YEAR = new Date().getFullYear();

function SocialLink({ href, icon: Icon }) {
return (
<a href={href} target="_blank" rel="noopener noreferrer">
<Icon />
</a>
);
}

function SocialLinks() {
return (
<div className="flex text-lg gap-3.5 float-right transition-opacity duration-300 hover:opacity-90">
<SocialLink href={socialLinks.twitter} icon={FaXTwitter} />
<SocialLink href={socialLinks.github} icon={FaGithub} />
<SocialLink href={socialLinks.instagram} icon={FaInstagram} />
<SocialLink href={socialLinks.linkedin} icon={FaLinkedinIn} />
<SocialLink href={socialLinks.email} icon={TbMailFilled} />
<a href="/rss.xml" target="_self">
<FaRss />
</a>
</div>
);
}

export default function Footer() {
return (
<small className="block lg:mt-24 mt-16 text-[#1C1C1C] dark:text-[#D4D4D4]">
<time>© {YEAR}</time>{" "}
<a
className="no-underline"
href={socialLinks.twitter}
target="_blank"
rel="noopener noreferrer"
>
{metaData.title}
</a>
<style jsx>{`
@media screen and (max-width: 480px) {
article {
padding-top: 2rem;
padding-bottom: 4rem;
}
}
`}</style>
<SocialLinks />
</small>
);
}
Loading

0 comments on commit 60fde63

Please sign in to comment.