Add and wire extended site env config
This commit is contained in:
@@ -1,8 +1,25 @@
|
|||||||
# Copy this file to `.env.local` and adjust values.
|
# Copy this file to `.env.local` and adjust values.
|
||||||
# All vars prefixed with NEXT_PUBLIC_ are exposed to the browser.
|
# All vars prefixed with NEXT_PUBLIC_ are exposed to the browser.
|
||||||
|
|
||||||
|
# Core site info
|
||||||
NEXT_PUBLIC_SITE_NAME="Your Name"
|
NEXT_PUBLIC_SITE_NAME="Your Name"
|
||||||
NEXT_PUBLIC_SITE_TITLE="Your Personal Site"
|
NEXT_PUBLIC_SITE_TITLE="Your Personal Site"
|
||||||
NEXT_PUBLIC_SITE_DESCRIPTION="Personal homepage and blog."
|
NEXT_PUBLIC_SITE_DESCRIPTION="Personal homepage and blog."
|
||||||
NEXT_PUBLIC_SITE_URL="https://example.com"
|
NEXT_PUBLIC_SITE_URL="https://example.com"
|
||||||
NEXT_PUBLIC_SITE_AUTHOR="Your Name"
|
NEXT_PUBLIC_SITE_AUTHOR="Your Name"
|
||||||
|
NEXT_PUBLIC_SITE_TAGLINE="Short tagline for your blog."
|
||||||
|
NEXT_PUBLIC_POSTS_PER_PAGE="5"
|
||||||
|
NEXT_PUBLIC_DEFAULT_LOCALE="zh-TW"
|
||||||
|
|
||||||
|
# Social and profile
|
||||||
|
NEXT_PUBLIC_TWITTER_HANDLE="@yourhandle"
|
||||||
|
NEXT_PUBLIC_GITHUB_URL="https://github.com/yourname"
|
||||||
|
NEXT_PUBLIC_LINKEDIN_URL="https://www.linkedin.com/in/yourname/"
|
||||||
|
NEXT_PUBLIC_EMAIL_CONTACT="you@example.com"
|
||||||
|
|
||||||
|
# SEO / Open Graph
|
||||||
|
NEXT_PUBLIC_OG_DEFAULT_IMAGE="/assets/og-default.jpg"
|
||||||
|
NEXT_PUBLIC_TWITTER_CARD_TYPE="summary_large_image"
|
||||||
|
|
||||||
|
# Analytics (public ID only)
|
||||||
|
NEXT_PUBLIC_ANALYTICS_ID=""
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { notFound } from 'next/navigation';
|
|||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import { allPosts } from 'contentlayer/generated';
|
import { allPosts } from 'contentlayer/generated';
|
||||||
import { getPostBySlug } from '@/lib/posts';
|
import { getPostBySlug } from '@/lib/posts';
|
||||||
|
import { siteConfig } from '@/lib/config';
|
||||||
|
|
||||||
export function generateStaticParams() {
|
export function generateStaticParams() {
|
||||||
return allPosts.map((post) => ({
|
return allPosts.map((post) => ({
|
||||||
@@ -44,7 +45,9 @@ export default function BlogPostPage({ params }: Props) {
|
|||||||
)}
|
)}
|
||||||
{post.published_at && (
|
{post.published_at && (
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
{new Date(post.published_at).toLocaleDateString('zh-TW')}
|
{new Date(post.published_at).toLocaleDateString(
|
||||||
|
siteConfig.defaultLocale
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{post.tags && (
|
{post.tags && (
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { getAllPostsSorted } from '@/lib/posts';
|
import { getAllPostsSorted } from '@/lib/posts';
|
||||||
|
import { siteConfig } from '@/lib/config';
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: 'Blog'
|
title: 'Blog'
|
||||||
@@ -22,7 +23,9 @@ export default function BlogIndexPage() {
|
|||||||
</Link>
|
</Link>
|
||||||
<div className="text-xs text-gray-500">
|
<div className="text-xs text-gray-500">
|
||||||
{post.published_at &&
|
{post.published_at &&
|
||||||
new Date(post.published_at).toLocaleDateString('zh-TW')}
|
new Date(post.published_at).toLocaleDateString(
|
||||||
|
siteConfig.defaultLocale
|
||||||
|
)}
|
||||||
{post.tags && post.tags.length > 0 && (
|
{post.tags && post.tags.length > 0 && (
|
||||||
<span className="ml-2">
|
<span className="ml-2">
|
||||||
{post.tags.map((t) => (
|
{post.tags.map((t) => (
|
||||||
@@ -47,4 +50,3 @@ export default function BlogIndexPage() {
|
|||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,22 @@ export const metadata: Metadata = {
|
|||||||
default: siteConfig.title,
|
default: siteConfig.title,
|
||||||
template: `%s | ${siteConfig.title}`
|
template: `%s | ${siteConfig.title}`
|
||||||
},
|
},
|
||||||
description: siteConfig.description
|
description: siteConfig.description,
|
||||||
|
metadataBase: new URL(siteConfig.url),
|
||||||
|
openGraph: {
|
||||||
|
title: siteConfig.title,
|
||||||
|
description: siteConfig.description,
|
||||||
|
url: siteConfig.url,
|
||||||
|
siteName: siteConfig.title,
|
||||||
|
images: [siteConfig.ogImage]
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: siteConfig.twitterCard,
|
||||||
|
site: siteConfig.social.twitter || undefined,
|
||||||
|
title: siteConfig.title,
|
||||||
|
description: siteConfig.description,
|
||||||
|
images: [siteConfig.ogImage]
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
@@ -18,7 +33,7 @@ export default function RootLayout({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<html lang="zh-Hant" suppressHydrationWarning>
|
<html lang={siteConfig.defaultLocale} suppressHydrationWarning>
|
||||||
<body>
|
<body>
|
||||||
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
||||||
<LayoutShell>{children}</LayoutShell>
|
<LayoutShell>{children}</LayoutShell>
|
||||||
@@ -27,4 +42,3 @@ export default function RootLayout({
|
|||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { getAllPostsSorted } from '@/lib/posts';
|
|||||||
import { siteConfig } from '@/lib/config';
|
import { siteConfig } from '@/lib/config';
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
const posts = getAllPostsSorted().slice(0, 5);
|
const posts = getAllPostsSorted().slice(0, siteConfig.postsPerPage);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="space-y-6">
|
<section className="space-y-6">
|
||||||
@@ -12,7 +12,7 @@ export default function HomePage() {
|
|||||||
你好,我是 {siteConfig.name}
|
你好,我是 {siteConfig.name}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mt-2 text-gray-600 dark:text-gray-300">
|
<p className="mt-2 text-gray-600 dark:text-gray-300">
|
||||||
這裡是我的個人首頁與技術 Blog。
|
{siteConfig.tagline}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -26,7 +26,9 @@ export default function HomePage() {
|
|||||||
</Link>
|
</Link>
|
||||||
{post.published_at && (
|
{post.published_at && (
|
||||||
<span className="ml-2 text-xs text-gray-500">
|
<span className="ml-2 text-xs text-gray-500">
|
||||||
{new Date(post.published_at).toLocaleDateString('zh-TW')}
|
{new Date(post.published_at).toLocaleDateString(
|
||||||
|
siteConfig.defaultLocale
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1,9 +1,55 @@
|
|||||||
import { siteConfig } from '@/lib/config';
|
import { siteConfig } from '@/lib/config';
|
||||||
|
|
||||||
export function SiteFooter() {
|
export function SiteFooter() {
|
||||||
|
const { social } = siteConfig;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className="border-t py-4 text-center text-sm text-gray-500">
|
<footer className="border-t py-4 text-center text-sm text-gray-500">
|
||||||
|
<div>
|
||||||
© {new Date().getFullYear()} {siteConfig.author}
|
© {new Date().getFullYear()} {siteConfig.author}
|
||||||
|
</div>
|
||||||
|
{(social.github || social.twitter || social.linkedin || social.email) && (
|
||||||
|
<div className="mt-1 space-x-3">
|
||||||
|
{social.github && (
|
||||||
|
<a
|
||||||
|
href={social.github}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="hover:underline"
|
||||||
|
>
|
||||||
|
GitHub
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{social.twitter && (
|
||||||
|
<a
|
||||||
|
href={`https://twitter.com/${social.twitter.replace('@', '')}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="hover:underline"
|
||||||
|
>
|
||||||
|
Twitter
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{social.linkedin && (
|
||||||
|
<a
|
||||||
|
href={social.linkedin}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="hover:underline"
|
||||||
|
>
|
||||||
|
LinkedIn
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{social.email && (
|
||||||
|
<a
|
||||||
|
href={`mailto:${social.email}`}
|
||||||
|
className="hover:underline"
|
||||||
|
>
|
||||||
|
Email
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</footer>
|
</footer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,5 +5,26 @@ export const siteConfig = {
|
|||||||
process.env.NEXT_PUBLIC_SITE_DESCRIPTION ||
|
process.env.NEXT_PUBLIC_SITE_DESCRIPTION ||
|
||||||
'Personal homepage and blog.',
|
'Personal homepage and blog.',
|
||||||
url: process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000',
|
url: process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000',
|
||||||
author: process.env.NEXT_PUBLIC_SITE_AUTHOR || 'Your Name'
|
author: process.env.NEXT_PUBLIC_SITE_AUTHOR || 'Your Name',
|
||||||
|
tagline:
|
||||||
|
process.env.NEXT_PUBLIC_SITE_TAGLINE ||
|
||||||
|
'這裡是我的個人首頁與技術 Blog。',
|
||||||
|
postsPerPage:
|
||||||
|
Number(process.env.NEXT_PUBLIC_POSTS_PER_PAGE) > 0
|
||||||
|
? Number(process.env.NEXT_PUBLIC_POSTS_PER_PAGE)
|
||||||
|
: 5,
|
||||||
|
defaultLocale: process.env.NEXT_PUBLIC_DEFAULT_LOCALE || 'zh-TW',
|
||||||
|
social: {
|
||||||
|
twitter: process.env.NEXT_PUBLIC_TWITTER_HANDLE || '',
|
||||||
|
github: process.env.NEXT_PUBLIC_GITHUB_URL || '',
|
||||||
|
linkedin: process.env.NEXT_PUBLIC_LINKEDIN_URL || '',
|
||||||
|
email: process.env.NEXT_PUBLIC_EMAIL_CONTACT || ''
|
||||||
|
},
|
||||||
|
ogImage: process.env.NEXT_PUBLIC_OG_DEFAULT_IMAGE || '/assets/og-default.jpg',
|
||||||
|
twitterCard:
|
||||||
|
(process.env.NEXT_PUBLIC_TWITTER_CARD_TYPE as
|
||||||
|
| 'summary'
|
||||||
|
| 'summary_large_image'
|
||||||
|
| undefined) || 'summary_large_image',
|
||||||
|
analyticsId: process.env.NEXT_PUBLIC_ANALYTICS_ID || ''
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user