feat: add links page to optimize SEO
|
|
@ -4,6 +4,8 @@ A minimalism, personal blog theme for Astro
|
|||
|
||||
[](https://astro.build) [](https://app.netlify.com/sites/astro-air/deploys)
|
||||
|
||||
<img src="/src/public/preview.png" alt="Astro Air" width="200">
|
||||
|
||||
>
|
||||
> If you find this project helpful, please consider giving it a star ⭐️
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import mdx from "@astrojs/mdx"
|
||||
import react from "@astrojs/react"
|
||||
import sitemap from "@astrojs/sitemap"
|
||||
import tailwind from "@astrojs/tailwind"
|
||||
import { defineConfig } from "astro/config"
|
||||
|
||||
import sitemap from "@astrojs/sitemap";
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
output: "static",
|
||||
|
|
@ -15,4 +14,4 @@ export default defineConfig({
|
|||
},
|
||||
},
|
||||
integrations: [react(), tailwind(), mdx(), sitemap()],
|
||||
})
|
||||
})
|
||||
|
|
|
|||
BIN
public/images/page-meta/en/about.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
public/images/page-meta/en/archive.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
public/images/page-meta/en/links.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
public/images/page-meta/zh/about.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
public/images/page-meta/zh/archive.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
public/images/page-meta/zh/links.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
23
public/links/astro.svg
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="logosandtypes_com" data-name="logosandtypes com" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 150 150">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: url(#linear-gradient);
|
||||
}
|
||||
</style>
|
||||
<linearGradient id="linear-gradient" x1="75.54" y1="113" x2="75.54" y2="143.15" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#ee46e0"/>
|
||||
<stop offset="1" stop-color="#da3737"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g id="Layer_2" data-name="Layer 2">
|
||||
<path id="Layer_3" data-name="Layer 3" class="cls-1" d="M0,0H150V150H0V0Z"/>
|
||||
</g>
|
||||
<path class="cls-2" d="M57.53,128.19c-5.86-5.34-7.57-16.57-5.13-24.7,4.23,5.13,10.1,6.75,16.17,7.67,10.41,1.56,21.21,.76,30.3-5.19,2.64,7.89-1.02,16.49-7.6,21.14-8.36,5.63-14.92,8.86-10.92,20.26-6.02-2.64-9.6-8.59-9.64-15-.02-1.6-.02-3.21-.24-4.79-.53-3.84-2.33-5.56-5.74-5.66-3.89-.16-6.6,2.57-7.21,6.27h0Z"/>
|
||||
<path d="M24.1,102.13s17.35-8.43,34.74-8.43l13.12-40.49c.49-1.96,1.92-3.29,3.54-3.29s3.05,1.33,3.54,3.29l13.12,40.49c20.6,0,34.74,8.43,34.74,8.43,0,0-29.47-80.07-29.52-80.23-.85-2.37-2.27-3.89-4.2-3.89H57.82c-1.92,0-3.29,1.52-4.2,3.89-.06,.16-29.53,80.23-29.53,80.23Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/preview.png
Normal file
|
After Width: | Height: | Size: 448 KiB |
32
src/components/astro/link-card.astro
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
interface Props {
|
||||
name: string
|
||||
link: string
|
||||
description: string
|
||||
avatar: string
|
||||
}
|
||||
|
||||
const { name, link, description, avatar } = Astro.props
|
||||
---
|
||||
|
||||
<a
|
||||
href={link}
|
||||
target="_blank"
|
||||
class="flex items-center gap-4 rounded-lg border border-neutral-200 p-4 hover:scale-[1.02] hover:bg-neutral-100 hover:shadow-lg dark:border-neutral-800 dark:hover:bg-neutral-900"
|
||||
>
|
||||
<img
|
||||
src={avatar}
|
||||
alt={name}
|
||||
class="h-12 w-12 rounded-full object-cover transition-transform duration-300 hover:scale-110"
|
||||
/>
|
||||
<div class="flex h-full flex-col">
|
||||
<span class="text-lg font-medium transition-colors">
|
||||
{name}
|
||||
</span>
|
||||
<span
|
||||
class="line-clamp-2 flex-1 text-sm text-neutral-600 dark:text-neutral-400"
|
||||
>
|
||||
{description}
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
|
|
@ -5,11 +5,13 @@ import { getLangFromUrl, useTranslations } from "~/i18n/utils"
|
|||
const lang = getLangFromUrl(Astro.url)
|
||||
const t = useTranslations(lang)
|
||||
|
||||
const { home, archive, custom, about } =
|
||||
const { home, archive, custom, links, about } =
|
||||
lang === "zh" ? zh.navigation : en.navigation
|
||||
---
|
||||
|
||||
<div class="mb-10 mt-4 flex gap-4 overflow-x-auto whitespace-nowrap text-lg">
|
||||
<div
|
||||
class="mb-10 mt-4 flex gap-4 overflow-x-auto whitespace-nowrap text-lg [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
||||
>
|
||||
{
|
||||
home && (
|
||||
<a
|
||||
|
|
@ -44,6 +46,17 @@ const { home, archive, custom, about } =
|
|||
</a>
|
||||
))
|
||||
}
|
||||
{
|
||||
links && (
|
||||
<a
|
||||
href={`/${lang}/links`}
|
||||
class="hover:underline hover:underline-offset-4"
|
||||
data-umami-event="nav-links"
|
||||
>
|
||||
<p>{t("nav.links")}</p>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
{
|
||||
about && (
|
||||
<a
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
export const title = "About Me"
|
||||
export const title = "Hello, I'm Guoqi Sun ~"
|
||||
|
||||
{title}
|
||||
<h3 class="my-10 text-center text-xl font-bold">{title}</h3>
|
||||
|
||||
<img
|
||||
className="block dark:hidden"
|
||||
|
|
|
|||
22
src/config/en/links.mdx
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
export const title = "My Link"
|
||||
|
||||
<h3 class="my-10 text-center text-xl font-bold">{title}</h3>
|
||||
|
||||
```json
|
||||
name: "Guoqi Sun"
|
||||
description: "Try, fail, retry. That's the rhythm of growth."
|
||||
link: "https://blog.sunguoqi.com"
|
||||
avatar: "https://assets.guoqi.dev/images/avatar.png"
|
||||
```
|
||||
|
||||
<h4 class="my-10 text-center">
|
||||
Want to add a friend link? Please leave a comment in the comment area below,
|
||||
and I will add it as soon as possible.
|
||||
</h4>
|
||||
|
||||
```json
|
||||
name: "your site name"
|
||||
description: "your site description"
|
||||
link: "your site link"
|
||||
avatar: "your site avatar"
|
||||
```
|
||||
|
|
@ -3,6 +3,7 @@ import { Github, Twitter } from "lucide-react"
|
|||
export const defaultLanguage: string = "en"
|
||||
|
||||
export const common = {
|
||||
domain: "https://astro-air.guoqi.dev",
|
||||
meta: {
|
||||
favicon: "/avatar.png",
|
||||
url: "https://blog.sunguoqi.com",
|
||||
|
|
@ -30,6 +31,7 @@ export const common = {
|
|||
link: "https://camlife.cn",
|
||||
},
|
||||
],
|
||||
links: true,
|
||||
about: true,
|
||||
},
|
||||
latestPosts: 8,
|
||||
|
|
@ -60,6 +62,28 @@ export const zh = {
|
|||
},
|
||||
],
|
||||
},
|
||||
pageMeta: {
|
||||
home: {
|
||||
title: "小孙同学",
|
||||
description: "读书、摄影、编程、旅行,热爱可抵岁月漫长~",
|
||||
ogImage: "/preview.png",
|
||||
},
|
||||
archive: {
|
||||
title: "归档",
|
||||
description: "小孙同学的所有文章",
|
||||
ogImage: "/images/page-meta/zh/archive.png",
|
||||
},
|
||||
links: {
|
||||
title: "朋友们",
|
||||
description: "小孙同学的和他朋友们",
|
||||
ogImage: "/images/page-meta/zh/links.png",
|
||||
},
|
||||
about: {
|
||||
title: "关于我",
|
||||
description: "小孙同学的自我介绍",
|
||||
ogImage: "/images/page-meta/zh/about.png",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const en = {
|
||||
|
|
@ -80,4 +104,26 @@ export const en = {
|
|||
},
|
||||
],
|
||||
},
|
||||
pageMeta: {
|
||||
home: {
|
||||
title: "Guoqi Sun",
|
||||
description: "Reading, Photography, Programming, Traveling",
|
||||
ogImage: "/preview.png",
|
||||
},
|
||||
archive: {
|
||||
title: "All Posts",
|
||||
description: "Here are Guoqi Sun's all posts",
|
||||
ogImage: "/images/page-meta/en/archive.png",
|
||||
},
|
||||
links: {
|
||||
title: "My Friends",
|
||||
description: "Here are Guoqi Sun's friends",
|
||||
ogImage: "/images/page-meta/en/links.png",
|
||||
},
|
||||
about: {
|
||||
title: "About Me",
|
||||
description: "Here is Guoqi Sun's self-introduction",
|
||||
ogImage: "/images/page-meta/en/about.png",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
14
src/config/links.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
export const links = [
|
||||
{
|
||||
name: "Astro",
|
||||
link: "https://astro.build",
|
||||
description: "The web framework for content-driven websites",
|
||||
avatar: "/links/astro.svg",
|
||||
},
|
||||
{
|
||||
name: "Guoqi Sun",
|
||||
link: "https://blog.sunguoqi.com",
|
||||
description: "Try, fail, retry. That's the rhythm of growth.",
|
||||
avatar: "https://assets.guoqi.dev/images/avatar.png",
|
||||
},
|
||||
]
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
export const title = "关于我"
|
||||
export const title = "你好,我是小孙同学~"
|
||||
|
||||
{title}
|
||||
<h3 class="my-10 text-center text-xl font-bold">{title}</h3>
|
||||
|
||||
<img
|
||||
className="block dark:hidden"
|
||||
|
|
|
|||
21
src/config/zh/links.mdx
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
export const title = "我的链接"
|
||||
|
||||
<h3 class="my-10 text-center text-xl font-bold">{title}</h3>
|
||||
|
||||
```json
|
||||
name: "Guoqi Sun"
|
||||
description: "Try, fail, retry. That's the rhythm of growth."
|
||||
link: "https://blog.sunguoqi.com"
|
||||
avatar: "https://assets.guoqi.dev/images/avatar.png"
|
||||
```
|
||||
|
||||
<h4 class="my-10 text-center">
|
||||
想要添加友情链接?请按照如下格式在评论区留言,我会及时添加。
|
||||
</h4>
|
||||
|
||||
```json
|
||||
name: "your site name"
|
||||
description: "your site description"
|
||||
link: "your site link"
|
||||
avatar: "your site avatar"
|
||||
```
|
||||
|
|
@ -12,17 +12,19 @@ export const ui = {
|
|||
"nav.home": "Home",
|
||||
"nav.archive": "Archive",
|
||||
"nav.about": "About",
|
||||
"nav.twitter": "Twitter",
|
||||
"nav.links": "Links",
|
||||
"blog.latest": "Latest Posts",
|
||||
"archive.title": "All Posts",
|
||||
"links.title": "My Friends",
|
||||
"tag.title": "Tag:",
|
||||
"tag.no_posts": "No posts found for tag",
|
||||
},
|
||||
zh: {
|
||||
"nav.home": "首页",
|
||||
"nav.about": "关于",
|
||||
"nav.blog": "推特",
|
||||
"nav.archive": "归档",
|
||||
"nav.links": "友链",
|
||||
"links.title": "朋友们",
|
||||
"blog.latest": "近期文章",
|
||||
"archive.title": "所有文章",
|
||||
"tag.title": "标签:",
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ const openGraphImage = !ogImage ? `/og/${filename}.png` : ogImage
|
|||
<Header />
|
||||
<Navigation />
|
||||
<slot />
|
||||
<div class="my-10">
|
||||
<div class="my-20">
|
||||
{needComment && <Comments />}
|
||||
</div>
|
||||
</main>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
---
|
||||
import { en, zh } from "~/config"
|
||||
import AboutContentEn from "~/config/en/about.mdx"
|
||||
import AboutContentZh from "~/config/zh/about.mdx"
|
||||
import { getLangFromUrl } from "~/i18n/utils"
|
||||
|
|
@ -10,18 +11,17 @@ export function getStaticPaths() {
|
|||
}
|
||||
|
||||
const lang = getLangFromUrl(Astro.url)
|
||||
const pageMeta = lang === "zh" ? zh.pageMeta : en.pageMeta
|
||||
const AboutContent = lang === "zh" ? AboutContentZh : AboutContentEn
|
||||
|
||||
const ogImage = "https://sunguoqi.com/me.png"
|
||||
---
|
||||
|
||||
<MainLayout
|
||||
title="about"
|
||||
description="test"
|
||||
ogImage={ogImage}
|
||||
title={pageMeta.about.title}
|
||||
description={pageMeta.about.description}
|
||||
ogImage={pageMeta.about.ogImage}
|
||||
needComment={true}
|
||||
>
|
||||
<div class="prose dark:prose-invert">
|
||||
<div class="prose max-w-3xl dark:prose-invert">
|
||||
<AboutContent />
|
||||
</div>
|
||||
</MainLayout>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
---
|
||||
import PostList from "~/components/astro/post-list.astro"
|
||||
import { en, zh } from "~/config"
|
||||
import { getLangFromUrl } from "~/i18n/utils"
|
||||
import MainLayout from "~/layouts/main.astro"
|
||||
import { getPostsByLocale } from "~/utils"
|
||||
import { getLanguagePaths } from "~/utils/langs"
|
||||
|
||||
const lang = getLangFromUrl(Astro.url)
|
||||
const pageMeta = lang === "zh" ? zh.pageMeta : en.pageMeta
|
||||
|
||||
export function getStaticPaths() {
|
||||
return getLanguagePaths()
|
||||
|
|
@ -28,7 +30,11 @@ const postsByYear = posts.reduce(
|
|||
const years = Object.keys(postsByYear).sort((a, b) => Number(b) - Number(a))
|
||||
---
|
||||
|
||||
<MainLayout>
|
||||
<MainLayout
|
||||
title={pageMeta.archive.title}
|
||||
description={pageMeta.archive.description}
|
||||
ogImage={pageMeta.archive.ogImage}
|
||||
>
|
||||
{
|
||||
years.map((year) => (
|
||||
<div class="year-group my-8">
|
||||
|
|
|
|||
|
|
@ -2,15 +2,24 @@
|
|||
import Footer from "~/components/astro/footer.astro"
|
||||
import Intro from "~/components/astro/intro.astro"
|
||||
import RecentBlogs from "~/components/astro/recent-blogs.astro"
|
||||
import { en, zh } from "~/config"
|
||||
import { getLangFromUrl } from "~/i18n/utils"
|
||||
import MainLayout from "~/layouts/main.astro"
|
||||
import { getLanguagePaths } from "~/utils/langs"
|
||||
|
||||
export function getStaticPaths() {
|
||||
return getLanguagePaths()
|
||||
}
|
||||
|
||||
const lang = getLangFromUrl(Astro.url)
|
||||
const pageMeta = lang === "zh" ? zh.pageMeta : en.pageMeta
|
||||
---
|
||||
|
||||
<MainLayout>
|
||||
<MainLayout
|
||||
title={pageMeta.home.title}
|
||||
description={pageMeta.home.description}
|
||||
ogImage={pageMeta.home.ogImage}
|
||||
>
|
||||
<Intro />
|
||||
<RecentBlogs />
|
||||
<Footer />
|
||||
|
|
|
|||
35
src/pages/[lang]/links/index.astro
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
import LinkCard from "~/components/astro/link-card.astro"
|
||||
import { en, zh } from "~/config"
|
||||
import LinksContentEn from "~/config/en/links.mdx"
|
||||
import { links } from "~/config/links"
|
||||
import LinksContentZh from "~/config/zh/links.mdx"
|
||||
import { getLangFromUrl, useTranslations } from "~/i18n/utils"
|
||||
import MainLayout from "~/layouts/main.astro"
|
||||
import { getLanguagePaths } from "~/utils/langs"
|
||||
|
||||
export function getStaticPaths() {
|
||||
return getLanguagePaths()
|
||||
}
|
||||
|
||||
const lang = getLangFromUrl(Astro.url)
|
||||
const t = useTranslations(lang)
|
||||
const LinksContent = lang === "zh" ? LinksContentZh : LinksContentEn
|
||||
const pageMeta = lang === "zh" ? zh.pageMeta : en.pageMeta
|
||||
---
|
||||
|
||||
<MainLayout
|
||||
title={pageMeta.links.title}
|
||||
description={pageMeta.links.description}
|
||||
ogImage={pageMeta.links.ogImage}
|
||||
needComment={true}
|
||||
>
|
||||
<h3 class="my-10 text-center text-xl font-bold">{t("links.title")}</h3>
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
{links.map((link) => <LinkCard {...link} />)}
|
||||
</div>
|
||||
<div class="prose max-w-3xl dark:prose-invert">
|
||||
<LinksContent />
|
||||
</div>
|
||||
</MainLayout>
|
||||
17
src/pages/robots.txt.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import type { APIRoute } from "astro"
|
||||
import { common } from "~/config"
|
||||
|
||||
const robots = `
|
||||
User-agent: *
|
||||
Disallow:
|
||||
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: ${new URL("sitemap-index.xml", common.domain).href}
|
||||
`.trim()
|
||||
|
||||
export const GET: APIRoute = () =>
|
||||
new Response(robots, {
|
||||
headers: { "Content-Type": "text/plain" },
|
||||
})
|
||||