✨ RSS & Sitemap
This commit is contained in:
parent
ea50f739b3
commit
86bb1d349a
@ -11,6 +11,8 @@ export interface RelatedAccount {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const SITE_NAME = "Goatshed";
|
export const SITE_NAME = "Goatshed";
|
||||||
|
export const SITE_DESCRIPTION = "山羊寒舍,在这里最终智羊工作室的最新动态。";
|
||||||
|
export const SITE_URL = "https://smartsheep.studio"
|
||||||
|
|
||||||
export const RELATED_ACCOUNTS: RelatedAccount[] = [
|
export const RELATED_ACCOUNTS: RelatedAccount[] = [
|
||||||
{ icon: <GitHubIcon />, platform: "GitHub", accountName: "@smartsheep-hq", link: "https://github.com/smartsheep-hq" },
|
{ icon: <GitHubIcon />, platform: "GitHub", accountName: "@smartsheep-hq", link: "https://github.com/smartsheep-hq" },
|
||||||
|
28
app/feed/route.ts
Normal file
28
app/feed/route.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import RSS from "rss";
|
||||||
|
import { SITE_DESCRIPTION, SITE_NAME, SITE_URL } from "@/app/consts";
|
||||||
|
import { getSortedPosts } from "@/content/posts";
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
const feed = new RSS({
|
||||||
|
title: SITE_NAME,
|
||||||
|
description: SITE_DESCRIPTION,
|
||||||
|
site_url: SITE_URL,
|
||||||
|
feed_url: `${SITE_URL}/feed`,
|
||||||
|
language: "zh-CN"
|
||||||
|
});
|
||||||
|
|
||||||
|
getSortedPosts().forEach((item) => {
|
||||||
|
feed.item({
|
||||||
|
url: `${SITE_URL}/p/${item.id}`,
|
||||||
|
title: item.title,
|
||||||
|
description: item.description ?? "No description yet.",
|
||||||
|
date: item.date,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Response(feed.xml(), {
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/xml"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@ -3,7 +3,7 @@ import { ReactNode } from "react";
|
|||||||
import { ThemeProvider } from "@mui/material/styles";
|
import { ThemeProvider } from "@mui/material/styles";
|
||||||
import { AppRouterCacheProvider } from "@mui/material-nextjs/v13-appRouter";
|
import { AppRouterCacheProvider } from "@mui/material-nextjs/v13-appRouter";
|
||||||
import { CssBaseline } from "@mui/material";
|
import { CssBaseline } from "@mui/material";
|
||||||
import { SITE_NAME } from "@/app/consts";
|
import { SITE_DESCRIPTION, SITE_NAME } from "@/app/consts";
|
||||||
import { theme } from "@/app/theme";
|
import { theme } from "@/app/theme";
|
||||||
|
|
||||||
import "@fontsource/roboto/300.css";
|
import "@fontsource/roboto/300.css";
|
||||||
@ -16,15 +16,18 @@ import "./globals.css";
|
|||||||
import AppShell from "@/components/AppShell";
|
import AppShell from "@/components/AppShell";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: SITE_NAME,
|
title: {
|
||||||
description: "山羊寒舍,在这里最终智羊工作室的最新动态。"
|
default: SITE_NAME,
|
||||||
|
template: `${SITE_NAME} | %s`
|
||||||
|
},
|
||||||
|
description: SITE_DESCRIPTION,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({ children }: Readonly<{
|
export default function RootLayout({ children }: Readonly<{
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="zh-CN">
|
||||||
<body>
|
<body>
|
||||||
<AppRouterCacheProvider>
|
<AppRouterCacheProvider>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
|
@ -30,7 +30,7 @@ export default function PostList() {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardActions sx={{ paddingX: 4, paddingBottom: 2 }}>
|
<CardActions sx={{ paddingX: 4, paddingBottom: 2 }}>
|
||||||
<Link href={`/posts/${post.id}`} passHref>
|
<Link href={`/p/${post.id}`} passHref>
|
||||||
<Button>Read more</Button>
|
<Button>Read more</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</CardActions>
|
</CardActions>
|
||||||
|
29
app/sitemap.ts
Normal file
29
app/sitemap.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { MetadataRoute } from "next";
|
||||||
|
import { getSortedPosts, Post } from "@/content/posts";
|
||||||
|
import { SITE_URL } from "@/app/consts";
|
||||||
|
|
||||||
|
export default function sitemap(): MetadataRoute.Sitemap {
|
||||||
|
const posts = getSortedPosts();
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
url: `${SITE_URL}/`,
|
||||||
|
lastModified: new Date(),
|
||||||
|
changeFrequency: "weekly",
|
||||||
|
priority: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: `${SITE_URL}/posts`,
|
||||||
|
lastModified: new Date(),
|
||||||
|
changeFrequency: "daily",
|
||||||
|
priority: 0.8
|
||||||
|
},
|
||||||
|
|
||||||
|
...posts.map((item: Post) => ({
|
||||||
|
url: `${SITE_URL}/posts/${item.id}`,
|
||||||
|
lastModified: item.date,
|
||||||
|
changeFrequency: "daily" as any,
|
||||||
|
priority: 0.75
|
||||||
|
}))
|
||||||
|
];
|
||||||
|
}
|
@ -18,18 +18,22 @@ import { theme } from "@/app/theme";
|
|||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import HomeIcon from "@mui/icons-material/Home";
|
import HomeIcon from "@mui/icons-material/Home";
|
||||||
import ArticleIcon from "@mui/icons-material/Article";
|
import ArticleIcon from "@mui/icons-material/Article";
|
||||||
|
import FeedIcon from "@mui/icons-material/RssFeed";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
export interface NavigationItem {
|
export interface NavigationItem {
|
||||||
icon: ReactNode;
|
icon?: ReactNode;
|
||||||
title: string;
|
title?: string;
|
||||||
link: string;
|
link?: string;
|
||||||
|
divider?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DRAWER_WIDTH = 320;
|
export const DRAWER_WIDTH = 320;
|
||||||
export const NAVIGATION_ITEMS: NavigationItem[] = [
|
export const NAVIGATION_ITEMS: NavigationItem[] = [
|
||||||
{ icon: <HomeIcon />, title: "首页", link: "/" },
|
{ icon: <HomeIcon />, title: "首页", link: "/" },
|
||||||
{ icon: <ArticleIcon />, title: "新闻", link: "/posts" },
|
{ icon: <ArticleIcon />, title: "新闻", link: "/posts" },
|
||||||
|
{ divider: true },
|
||||||
|
{ icon: <FeedIcon />, title: "订阅源", link: "/feed" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const AppNavigationHeader = styled("div")(({ theme }) => ({
|
export const AppNavigationHeader = styled("div")(({ theme }) => ({
|
||||||
@ -57,14 +61,16 @@ export function AppNavigation({ showClose, onClose }: {
|
|||||||
</AppNavigationHeader>
|
</AppNavigationHeader>
|
||||||
<Divider />
|
<Divider />
|
||||||
<List>
|
<List>
|
||||||
{NAVIGATION_ITEMS.map((item, idx) => (
|
{NAVIGATION_ITEMS.map((item, idx) => {
|
||||||
<Link key={idx} href={item.link} passHref>
|
return item.divider ? <Divider /> : (
|
||||||
<ListItemButton>
|
<Link key={idx} href={item.link ?? "/"} passHref>
|
||||||
<ListItemIcon>{item.icon}</ListItemIcon>
|
<ListItemButton>
|
||||||
<ListItemText primary={item.title} />
|
<ListItemIcon>{item.icon}</ListItemIcon>
|
||||||
</ListItemButton>
|
<ListItemText primary={item.title} />
|
||||||
</Link>
|
</ListItemButton>
|
||||||
))}
|
</Link>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</List>
|
</List>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -5,9 +5,18 @@ const nextConfig = {
|
|||||||
remotePatterns: [
|
remotePatterns: [
|
||||||
{
|
{
|
||||||
protocol: "https",
|
protocol: "https",
|
||||||
hostname: "**",
|
hostname: "**"
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
|
},
|
||||||
|
async rewrites() {
|
||||||
|
return [
|
||||||
|
{ source: "/rss", destination: "/feed" },
|
||||||
|
{ source: "/rss.xml", destination: "/feed.xml" },
|
||||||
|
{ source: "/feed.xml", destination: "/feed" },
|
||||||
|
|
||||||
|
{ source: "/p/:id", destination: "/posts/:id" }
|
||||||
|
];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -24,13 +24,15 @@
|
|||||||
"next": "14.1.0",
|
"next": "14.1.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"react-markdown": "^9.0.1"
|
"react-markdown": "^9.0.1",
|
||||||
|
"rss": "^1.2.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/typography": "^0.5.10",
|
"@tailwindcss/typography": "^0.5.10",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
|
"@types/rss": "^0.0.32",
|
||||||
"autoprefixer": "^10.0.1",
|
"autoprefixer": "^10.0.1",
|
||||||
"eslint": "^8",
|
"eslint": "^8",
|
||||||
"eslint-config-next": "14.1.0",
|
"eslint-config-next": "14.1.0",
|
||||||
|
Loading…
Reference in New Issue
Block a user