Compare commits

...

5 Commits

Author SHA1 Message Date
Anthony Berg
3e4946c3f7 fix(build): remove edge runtime route segment for mdxPath 2025-04-18 14:32:39 +02:00
Anthony Berg
088ed7ac51 fix(build): add edge runtime route segment 2025-04-18 14:24:09 +02:00
Anthony Berg
32099d12a3 fix(style): revert to previous colours 2025-04-18 14:23:06 +02:00
Anthony Berg
ef4c5dfa07 feat(blog): add first blog post 2025-04-18 14:13:23 +02:00
Anthony Berg
5080bbff7c feat(build): update to nextra v4 2025-04-18 14:13:13 +02:00
24 changed files with 1906 additions and 1165 deletions

3
.gitignore vendored
View File

@ -35,5 +35,8 @@ yarn-error.log*
*.tsbuildinfo *.tsbuildinfo
next-env.d.ts next-env.d.ts
# nextra
_pagefind/
# IDEA folder # IDEA folder
/.idea/ /.idea/

View File

@ -2,13 +2,15 @@ import nextra from 'nextra'
import type { NextConfig } from 'next' import type { NextConfig } from 'next'
const withNextra = nextra({ const withNextra = nextra({
theme: 'nextra-theme-blog', // ... Other Nextra config options
themeConfig: './src/theme.config.tsx', search: false
// optional: add `unstable_staticImage: true` to enable Nextra's auto image import
}) })
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
// any configs you need // any configs you need
turbopack: {
resolveExtensions: ['.mdx', '.tsx', '.ts', '.jsx', '.js', '.mjs', '.json'],
},
} }
export default withNextra(nextConfig) export default withNextra(nextConfig)

View File

@ -6,9 +6,10 @@
"private": true, "private": true,
"scripts": { "scripts": {
"preinstall": "npx only-allow pnpm", "preinstall": "npx only-allow pnpm",
"dev": "next dev", "dev": "next dev --turbopack",
"build": "next build", "build": "next build",
"start": "next start" "start": "next start",
"postbuild": "pagefind --site .next/server/app --output-path public/_pagefind"
}, },
"engines": { "engines": {
"node": ">=20.x", "node": ">=20.x",
@ -16,16 +17,18 @@
}, },
"dependencies": { "dependencies": {
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"next": "^15.0.2", "next": "^15.3.1",
"nextra": "^3.2.0", "nextra": "^4.2.17",
"nextra-theme-blog": "^3.2.0", "nextra-theme-blog": "^4.2.17",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1" "react-dom": "^18.3.1"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.8.4", "@types/mdx": "^2.0.13",
"@types/react": "^18.3.12", "@types/node": "^22.14.1",
"@types/react-dom": "^18.3.1", "@types/react": "^19.1.2",
"typescript": "^5.6.3" "@types/react-dom": "^19.1.2",
"pagefind": "^1.3.0",
"typescript": "^5.8.3"
} }
} }

2606
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,30 @@
import { generateStaticParamsFor, importPage } from 'nextra/pages'
import { useMDXComponents as getMDXComponents } from '../../mdx-components'
export const generateStaticParams = generateStaticParamsFor('mdxPath')
type Props = {
params: Promise<{
mdxPath: string[]
}>
}
export async function generateMetadata(props: Props) {
const params = await props.params
const { metadata } = await importPage(params.mdxPath)
return metadata
}
const Wrapper = getMDXComponents().wrapper
export default async function Page(props: Props) {
const params = await props.params
const result = await importPage(params.mdxPath)
const { default: MDXContent, toc, metadata } = result
return (
<Wrapper toc={toc} metadata={metadata}>
<MDXContent {...props} params={params} />
</Wrapper>
)
}

55
src/app/layout.tsx Normal file
View File

@ -0,0 +1,55 @@
import { Layout, Navbar, ThemeSwitch } from 'nextra-theme-blog';
import { Head, Search } from 'nextra/components';
import { getPageMap } from 'nextra/page-map';
import 'nextra-theme-blog/style.css';
import React from 'react';
import { Metadata } from 'next';
import Footer from '@/components/footer'
export const metadata: Metadata = {
title: {
default: 'Anthony Berg\'s Portfolio',
template: '%s | Anthony Berg',
},
description: 'My own personal portfolio including for my own projects related to computer science',
openGraph: {
url: 'https://anthonyberg.io',
siteName: 'Anthony Berg\'s Website',
locale: 'en_GB',
type: 'website',
images: [{
url: '/images/kgxtunnel.jpg',
}]
}
}
const YEAR = new Date().getFullYear()
export default async function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html
lang="en"
dir="ltr"
suppressHydrationWarning
>
<Head backgroundColor={{dark: '#111111', light: '#fafafa'}} />
<body>
<Layout>
<Navbar pageMap={await getPageMap()}>
{/*<Search placeholder={"Search..."}/>*/}
<ThemeSwitch />
</Navbar>
{children}
<Footer />
</Layout>
</body>
</html>
);
}

View File

@ -0,0 +1,18 @@
import { normalizePages } from 'nextra/normalize-pages'
import { getPageMap } from 'nextra/page-map'
export async function getPosts() {
const { directories } = normalizePages({
list: await getPageMap('/posts'),
route: '/posts'
})
return directories
.filter(post => post.name !== 'index')
.sort((a, b) => (new Date(b.frontMatter.date)).valueOf() - (new Date(a.frontMatter.date)).valueOf())
}
export async function getTags() {
const posts = await getPosts()
const tags = posts.flatMap(post => post.frontMatter.tags)
return tags
}

36
src/app/posts/page.tsx Normal file
View File

@ -0,0 +1,36 @@
import Link from 'next/link'
import { PostCard } from 'nextra-theme-blog'
import { getPosts, getTags } from './get-posts'
export const metadata = {
title: 'Posts'
}
export default async function PostsPage() {
const tags = await getTags()
const posts = await getPosts()
const allTags: Record<string, number> = Object.create(null)
for (const tag of tags) {
allTags[tag] ??= 0
allTags[tag] += 1
}
return (
<div data-pagefind-ignore="all">
<h1>{metadata.title}</h1>
<div
className="not-prose"
style={{ display: 'flex', flexWrap: 'wrap', gap: '.5rem' }}
>
{Object.entries(allTags).map(([tag, count]) => (
<Link key={tag} href={`/tags/${tag}`} className="nextra-tag">
{tag} ({count})
</Link>
))}
</div>
{posts.map(post => (
<PostCard key={post.route} post={post} />
))}
</div>
)
}

41
src/app/rss.xml/route.ts Normal file
View File

@ -0,0 +1,41 @@
import { getPosts } from '../posts/get-posts'
export const runtime = 'edge';
const CONFIG = {
title: 'Anthony\'s Blog',
siteUrl: 'https://anthonyberg.io',
description: 'Anthony\'s latest blog posts about tech related things.',
lang: 'en-gb'
}
export async function GET() {
const allPosts = await getPosts()
const posts = allPosts
.map(
post => ` <item>
<title>${post.title}</title>
<description>${post.frontMatter.description}</description>
<link>${CONFIG.siteUrl}${post.route}</link>
<pubDate>${new Date(post.frontMatter.date).toUTCString()}</pubDate>
</item>`
)
.join('\n')
const xml = `<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title>${CONFIG.title}</title>
<link>${CONFIG.siteUrl}</link>
<description>${CONFIG.description}</description>
<language>${CONFIG.lang}</language>
${posts}
</channel>
</rss>`
return new Response(xml, {
headers: {
'Content-Type': 'application/rss+xml'
}
})
}

View File

@ -0,0 +1,33 @@
import { PostCard } from 'nextra-theme-blog'
import { getPosts, getTags } from '../../posts/get-posts'
export async function generateMetadata(props: { params: any }) {
const params = await props.params
return {
title: `Posts Tagged with “${decodeURIComponent(params.tag)}`
}
}
export async function generateStaticParams() {
const allTags = await getTags()
return [...new Set(allTags)].map(tag => ({ tag }))
}
export default async function TagPage(props: { params: any }) {
const params = await props.params
const { title } = await generateMetadata({ params })
const posts = await getPosts()
return (
<>
<h1>{title}</h1>
{posts
.filter(post =>
post.frontMatter.tags.includes(decodeURIComponent(params.tag))
)
.map(post => (
<PostCard key={post.route} post={post} />
))}
</>
)
}

29
src/components/footer.tsx Normal file
View File

@ -0,0 +1,29 @@
'use client';
import Link from 'next/link';
import { Footer as NXFooter } from 'nextra-theme-blog'
function Footer() {
const YEAR = new Date().getFullYear();
return (
<NXFooter>
<small>
<Link href={"https://github.com/smyalygames/anthonyberg-website/blob/main/LICENSE"}>
<time>{YEAR}</time>
© Anthony Berg.</Link>
<a href={"https://github.com/smyalygames/anthonyberg-website/"}>GitHub Repository</a>
</small>
<style jsx>{`
footer {
margin-top: 8rem;
}
a {
float: right;
}
`}</style>
</NXFooter>
);
}
export default Footer;

View File

@ -1,3 +1,5 @@
"use client"
import styles from './theme-image.module.css' import styles from './theme-image.module.css'
import Image, { ImageProps } from 'next/image' import Image, { ImageProps } from 'next/image'

24
src/content/_meta.ts Normal file
View File

@ -0,0 +1,24 @@
import type { MetaRecord } from 'nextra';
const meta: MetaRecord = {
index: {
title: 'Home',
type: 'page',
},
projects: {
title: 'Projects',
type: 'page',
},
posts: {
title: 'Blog',
type: 'page'
},
veganenumbers: {
display: 'hidden',
theme: {
sidebar: false
}
}
}
export default meta;

View File

@ -1,22 +1,18 @@
--- ---
type: page
title: Anthony Berg title: Anthony Berg
date: 2024-10-05
--- ---
import {ThemeImage} from '@/components/theme-image' import { ThemeImage } from '@/components/theme-image'
# Anthony Berg
Hey, welcome to my website! :D Hey, welcome to my website! :D
I'm Anthony, and I enjoy working with computers, be it from developing on my own projects, I'm Anthony, and I enjoy working with computers, be it from developing on my own projects,
or just using Linux on my PCs or setting up my own personal servers. or just using Linux on my PCs or setting up my own personal servers.
For my day to day life, I am currently in my 1<sup>st</sup> year of my Master's at OsloMet, studying For my day-to-day life, I am currently in my 1<sup>st</sup> year of my Master's at OsloMet, studying
Applied Computer and Information Technology, specialising in Mathematical Modelling and Scientific Applied Computer and Information Technology, specialising in Mathematical Modelling and Scientific
Computing. Computing.
I have also completed BSc Computer Science at Newcastle University. I have also completed B.Sc. Computer Science at Newcastle University.
I have a few open source projects that I have worked on that you can find in my [portfolio](/projects). I have a few open source projects that I have worked on that you can find in my [portfolio](/projects).

View File

@ -0,0 +1,19 @@
---
title: First Post
tags: [Blog]
author: Anthony Berg
date: 2025-04-18
description: I might as well start out a blog.
---
This is my first post, I thought it was about time to actually start it.
Now that I have updated this website to Nextra v4, it is about time that I actually
start to provide some content for the blog.
What am I going to do with my blog? I think the direction I would head in with it
is whenever I feel like I struggled doing something, and learnt how to do it well,
I would actually provide a guide on it, just in case someone else would find it interesting.
Or maybe just an interesting topic that I found during my studies, as I am currently
working with HPCs.
But hopefully it will not be too long before the next real blog post comes out!

View File

@ -1,13 +1,9 @@
--- ---
type: page
title: Projects title: Projects
date: 2023-11-14
--- ---
import Image from "next/image"; import Image from "next/image";
# Projects
Here are some of my projects that I have worked on Here are some of my projects that I have worked on

13
src/mdx-components.ts Normal file
View File

@ -0,0 +1,13 @@
import { useMDXComponents as getThemeComponents } from 'nextra-theme-blog';
import type { MDXComponents } from 'mdx/types';
// Get the default MDX components
const themeComponents = getThemeComponents();
// Merge components
export function useMDXComponents(components?: MDXComponents) {
return {
...themeComponents,
...components
}
}

View File

@ -1,6 +0,0 @@
import type { AppProps } from 'next/app'
import '../styles/main.css'
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}

View File

@ -1,9 +0,0 @@
---
type: posts
title: Posts
date: 2023-11-14
---
# Posts
## This is currently under construction

View File

@ -1,13 +0,0 @@
---
type: tag
title: Tagged Posts
---
import { useRouter } from 'next/router'
export const TagName = () => {
const { tag } = useRouter().query
return tag || null
}
# Posts Tagged with “<TagName/>”

View File

@ -1,57 +0,0 @@
import Link from "next/link";
const YEAR = new Date().getFullYear()
const websiteMeta = {
title: 'Anthony Berg\'s Portfolio',
description: 'My own personal portfolio including for my own projects related to computer science',
image: '/images/kgxtunnel.jpg',
}
export default {
footer: (
<footer>
<small>
<Link href={"https://github.com/smyalygames/anthonyberg-website/blob/main/LICENSE"}><time>{YEAR}</time> © Anthony Berg.</Link>
<a href={"https://github.com/smyalygames/anthonyberg-website/"}>GitHub Repository</a>
</small>
<style jsx>{`
footer {
margin-top: 8rem;
}
a {
float: right;
}
`}</style>
</footer>
),
head: ({ title, meta } : { title: string; meta: any }) => (
<>
<link
rel="preload"
href="/fonts/Inter-roman.latin.var.woff2"
as="font"
type="font/woff2"
crossOrigin="anonymous"
/>
{meta.description && (
<meta name="description" content={meta.description}/>
)}
{meta.tag && <meta name="keywords" content={meta.tag}/>}
{meta.author && <meta name="author" content={meta.author}/>}
{/*og meta*/}
<meta name="robots" content="follow, index"/>
<meta name="description" content={websiteMeta.description}/>
<meta property="og:site_name" content={websiteMeta.title}/>
<meta property="og:description" content={websiteMeta.description}/>
<meta property="og:title" content={websiteMeta.title}/>
<meta property="og:image" content={websiteMeta.image}/>
<meta name="twitter:card" content="summary_large_image"/>
{/*<meta name="twitter:site" content="@yourname" />*/}
<meta name="twitter:title" content={websiteMeta.title}/>
<meta name="twitter:description" content={websiteMeta.description}/>
<meta name="twitter:image" content={websiteMeta.image}/>
</>
)
}

View File

@ -1,7 +1,11 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es5", "target": "es2020",
"lib": ["dom", "dom.iterable", "esnext"], "lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"strict": true, "strict": true,
@ -9,15 +13,29 @@
"noEmit": true, "noEmit": true,
"esModuleInterop": true, "esModuleInterop": true,
"module": "esnext", "module": "esnext",
"moduleResolution": "node", "moduleResolution": "bundler",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"jsx": "preserve", "jsx": "preserve",
"incremental": true, "incremental": true,
"paths": { "paths": {
"@/components/*": ["./src/components/*"] "@/components/*": [
} "./src/components/*"
]
},
"plugins": [
{
"name": "next"
}
]
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "include": [
"exclude": ["node_modules"] "next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
} }