Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,44 @@ import sitemap from '@astrojs/sitemap'
import rehypeExternalLinks from 'rehype-external-links'
import rehypeOptimizeImages from './src/utils/rehype-optimize-images'
import rehypeInjectAds from './src/utils/rehype-inject-ads'
import { existsSync, readdirSync } from 'node:fs'
import { fileURLToPath } from 'node:url'

const defaultLocale = 'ja'
const locales = ['ja', 'en', 'zh-cn', 'es', 'pt', 'fr', 'ko', 'de', 'ru']
const translatedBlogSlugsByLocale = Object.fromEntries(
locales
.filter((locale) => locale !== defaultLocale)
.map((locale) => {
const dir = fileURLToPath(
new URL(`./src/content/blog/${locale}/`, import.meta.url),
)
return [
locale,
new Set(
existsSync(dir)
? readdirSync(dir)
.filter((file) => file.endsWith('.md'))
.map((file) => file.replace(/\.md$/, ''))
: [],
),
]
}),
)

function isMissingLocalizedBlogPost(page) {
const { pathname } = new URL(page)
const parts = pathname.split('/').filter(Boolean)
const [locale, section, slug] = parts
return (
locale &&
locale !== defaultLocale &&
translatedBlogSlugsByLocale[locale] &&
section === 'blog' &&
parts.length === 3 &&
!translatedBlogSlugsByLocale[locale].has(decodeURIComponent(slug))
)
}

export default defineConfig({
site: 'https://acecore.net',
Expand Down Expand Up @@ -33,6 +71,7 @@ export default defineConfig({
lastmod: new Date(),
filter(page) {
return (
!isMissingLocalizedBlogPost(page) &&
!page.includes('/blog/tags/') &&
!page.includes('/blog/archive/') &&
!page.includes('/blog/authors/') &&
Expand Down
32 changes: 32 additions & 0 deletions public/_redirects
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,38 @@
/de/blog/business-system-implementation-tips/ https://systems.acecore.net/guide/ 301
/ru/blog/business-system-implementation-tips https://systems.acecore.net/guide/ 301
/ru/blog/business-system-implementation-tips/ https://systems.acecore.net/guide/ 301
/en/blog/homepage-production-cost-guide https://acecore.net/blog/homepage-production-cost-guide/ 301
/en/blog/homepage-production-cost-guide/ https://acecore.net/blog/homepage-production-cost-guide/ 301
/zh-cn/blog/homepage-production-cost-guide https://acecore.net/blog/homepage-production-cost-guide/ 301
/zh-cn/blog/homepage-production-cost-guide/ https://acecore.net/blog/homepage-production-cost-guide/ 301
/es/blog/homepage-production-cost-guide https://acecore.net/blog/homepage-production-cost-guide/ 301
/es/blog/homepage-production-cost-guide/ https://acecore.net/blog/homepage-production-cost-guide/ 301
/pt/blog/homepage-production-cost-guide https://acecore.net/blog/homepage-production-cost-guide/ 301
/pt/blog/homepage-production-cost-guide/ https://acecore.net/blog/homepage-production-cost-guide/ 301
/fr/blog/homepage-production-cost-guide https://acecore.net/blog/homepage-production-cost-guide/ 301
/fr/blog/homepage-production-cost-guide/ https://acecore.net/blog/homepage-production-cost-guide/ 301
/ko/blog/homepage-production-cost-guide https://acecore.net/blog/homepage-production-cost-guide/ 301
/ko/blog/homepage-production-cost-guide/ https://acecore.net/blog/homepage-production-cost-guide/ 301
/de/blog/homepage-production-cost-guide https://acecore.net/blog/homepage-production-cost-guide/ 301
/de/blog/homepage-production-cost-guide/ https://acecore.net/blog/homepage-production-cost-guide/ 301
/ru/blog/homepage-production-cost-guide https://acecore.net/blog/homepage-production-cost-guide/ 301
/ru/blog/homepage-production-cost-guide/ https://acecore.net/blog/homepage-production-cost-guide/ 301
/en/blog/service-cta-contact-prefill https://acecore.net/blog/service-cta-contact-prefill/ 301
/en/blog/service-cta-contact-prefill/ https://acecore.net/blog/service-cta-contact-prefill/ 301
/zh-cn/blog/service-cta-contact-prefill https://acecore.net/blog/service-cta-contact-prefill/ 301
/zh-cn/blog/service-cta-contact-prefill/ https://acecore.net/blog/service-cta-contact-prefill/ 301
/es/blog/service-cta-contact-prefill https://acecore.net/blog/service-cta-contact-prefill/ 301
/es/blog/service-cta-contact-prefill/ https://acecore.net/blog/service-cta-contact-prefill/ 301
/pt/blog/service-cta-contact-prefill https://acecore.net/blog/service-cta-contact-prefill/ 301
/pt/blog/service-cta-contact-prefill/ https://acecore.net/blog/service-cta-contact-prefill/ 301
/fr/blog/service-cta-contact-prefill https://acecore.net/blog/service-cta-contact-prefill/ 301
/fr/blog/service-cta-contact-prefill/ https://acecore.net/blog/service-cta-contact-prefill/ 301
/ko/blog/service-cta-contact-prefill https://acecore.net/blog/service-cta-contact-prefill/ 301
/ko/blog/service-cta-contact-prefill/ https://acecore.net/blog/service-cta-contact-prefill/ 301
/de/blog/service-cta-contact-prefill https://acecore.net/blog/service-cta-contact-prefill/ 301
/de/blog/service-cta-contact-prefill/ https://acecore.net/blog/service-cta-contact-prefill/ 301
/ru/blog/service-cta-contact-prefill https://acecore.net/blog/service-cta-contact-prefill/ 301
/ru/blog/service-cta-contact-prefill/ https://acecore.net/blog/service-cta-contact-prefill/ 301
/blog/tags/システム開発 https://systems.acecore.net/guide/ 301
/blog/tags/システム開発/ https://systems.acecore.net/guide/ 301
/en/blog/tags/システム開発 https://systems.acecore.net/guide/ 301
Expand Down
13 changes: 9 additions & 4 deletions src/layouts/BaseLayout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ interface Props {
locale?: Locale
breadcrumbItems?: { name: string; item?: string }[]
languageSwitcherPath?: string
canonicalPath?: string
alternateUrls?: { locale: Locale; url: string }[]
}

const locale = Astro.props.locale ?? getLocaleFromUrl(Astro.url)
Expand All @@ -44,12 +46,14 @@ const {
preloadImage,
breadcrumbItems,
languageSwitcherPath,
canonicalPath,
alternateUrls,
} = Astro.props

const isHome = title === t(locale, 'nav.home')
const gaFallbackDelay = isHome ? 12000 : 4000
const pageTitle = isHome ? t(locale, 'meta.siteTitle') : `${title} | Acecore`
const canonicalURL = new URL(Astro.url.pathname, Astro.site)
const canonicalURL = new URL(canonicalPath ?? Astro.url.pathname, Astro.site)
const ogImage = image || new URL('/uploads/og-default.png', Astro.site).href
const isMonetizedPage =
Astro.url.pathname === '/blog/' || Astro.url.pathname.startsWith('/blog/')
Expand Down Expand Up @@ -146,7 +150,8 @@ const breadcrumbLd =
}
: null)

const alternateUrls = getAlternateUrls(Astro.url.pathname, siteOrigin)
const alternateUrlEntries =
alternateUrls ?? getAlternateUrls(Astro.url.pathname, siteOrigin)

import { ClientRouter } from 'astro:transitions'
import Header from '../components/Header.astro'
Expand Down Expand Up @@ -220,14 +225,14 @@ import { SITE } from '../data/site'
href={locale === defaultLocale ? '/rss.xml' : `/${locale}/rss.xml`}
/>
{
alternateUrls.map(({ locale: l, url }) => (
alternateUrlEntries.map(({ locale: l, url }) => (
<link rel="alternate" hreflang={htmlLangMap[l]} href={url} />
))
}
<link
rel="alternate"
hreflang="x-default"
href={alternateUrls.find((a) => a.locale === defaultLocale)?.url}
href={alternateUrlEntries.find((a) => a.locale === defaultLocale)?.url}
/>
<link rel="preconnect" href={SITE.url} />
{
Expand Down
16 changes: 11 additions & 5 deletions src/pages/[locale]/blog/[...slug].astro
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,22 @@ import { locales, defaultLocale, type Locale } from '../../../i18n'
import { isBasePost } from '../../../utils/blog-i18n'

export async function getStaticPaths() {
const allPosts = (await getCollection('blog'))
const allRawPosts = await getCollection('blog')
const allPosts = allRawPosts
.filter(isBasePost)
.sort((a, b) => b.data.date.getTime() - a.data.date.getTime())
const translatedPostIds = new Set(
allRawPosts.filter((post) => !isBasePost(post)).map((post) => post.id),
)
return locales
.filter((l) => l !== defaultLocale)
.flatMap((locale) =>
allPosts.map((post) => ({
params: { locale, slug: post.id },
props: { post, allPosts },
})),
allPosts
.filter((post) => translatedPostIds.has(`${locale}/${post.id}`))
.map((post) => ({
params: { locale, slug: post.id },
props: { post, allPosts },
})),
)
}

Expand Down
23 changes: 21 additions & 2 deletions src/views/BlogPostPage.astro
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
getDateParts,
defaultLocale,
htmlLangMap,
locales,
type Locale,
} from '../i18n'
import { getCollection } from 'astro:content'
Expand Down Expand Up @@ -64,6 +65,21 @@ const authors = await getAllAuthors()
const tagsData = await getAllTags()
const post = localizePost(basePost, allRawPosts, locale)
const baseSlug = basePost.id
const isFallbackLocalePost = locale !== defaultLocale && post.id === basePost.id
const canonicalPostPath = isFallbackLocalePost
? `/blog/${baseSlug}/`
: u(`/blog/${baseSlug}/`)
const canonicalPostUrl = new URL(canonicalPostPath, Astro.site).href
const alternateUrls = locales
.filter(
(l) =>
l === defaultLocale ||
allRawPosts.some((candidate) => candidate.id === `${l}/${baseSlug}`),
)
.map((l) => ({
locale: l,
url: new URL(getLocalizedUrl(`/blog/${baseSlug}/`, l), Astro.site).href,
}))

const { Content, headings } = await render(post)
const tocHeadings = headings.filter((h) => h.depth >= 2 && h.depth <= 3)
Expand Down Expand Up @@ -145,7 +161,7 @@ const blogPostingLd = {
},
mainEntityOfPage: {
'@type': 'WebPage',
'@id': `https://acecore.net/blog/${baseSlug}/`,
'@id': canonicalPostUrl,
},
inLanguage: htmlLangMap[locale],
}
Expand Down Expand Up @@ -229,9 +245,12 @@ const ctaHeading = isDefaultLocale
jsonLd={jsonLdData}
publishedTime={post.data.date}
modifiedTime={post.data.lastUpdated}
robots={isFallbackLocalePost ? 'noindex, follow' : 'index, follow'}
tags={post.data.tags}
author={author?.name}
locale={locale}
canonicalPath={canonicalPostPath}
alternateUrls={alternateUrls}
preloadImage={postImage
? {
src: optimizeImage(postImage),
Expand Down Expand Up @@ -397,7 +416,7 @@ const ctaHeading = isDefaultLocale

<div class="my-10 border-t border-slate-200 py-6">
<ShareButtons
url={`https://acecore.net/blog/${baseSlug}/`}
url={canonicalPostUrl}
title={post.data.title}
locale={locale}
/>
Expand Down
Loading