Skip to content

Commit aafae3d

Browse files
committed
fix: address PR review findings across website and websocket code
Website/SEO fixes: - Fix hreflang duplication: use router.locales/defaultLocale and strip locale prefix from path before building alternates - Escape JSON-LD output to prevent script-breakout via frontmatter - Remove references to non-existent favicon PNGs, use existing robynog.png - Add aria-current="page" to active breadcrumb item in blog posts - Guard getStaticProps/getBlogPostBySlug against missing files (return notFound: true) - Use siteUrl variable in next-sitemap alternateRefs instead of hardcoded URL - Strip query strings and hash fragments in breadcrumb path builder - Wrap fetchStars in useCallback and add to useEffect dependency array - Use dynamic locale for docs link in 404 page - Extract shared formatDate utility to deduplicate blog components Rust/Python fixes: - Rename sender_id → recipient_id in robyn.pyi stubs to match Rust bindings - Remove repr() logging from extract_ws_return to prevent data leakage - Add WsPayload::Close variant to replace "Connection closed" magic string - Switch WebSocket message channel from unbounded to bounded (cap 256) - Log warning when bounded channel is full/closed instead of silently dropping - Propagate registry try_send errors to Python as exceptions instead of swallowing with Ok(()) Made-with: Cursor
1 parent 5794c8d commit aafae3d

15 files changed

Lines changed: 116 additions & 115 deletions

File tree

docs_src/next-sitemap.config.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
/** @type {import('next-sitemap').IConfig} */
2+
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://robyn.tech'
3+
24
module.exports = {
3-
siteUrl: process.env.NEXT_PUBLIC_SITE_URL || 'https://robyn.tech',
5+
siteUrl,
46
generateRobotsTxt: false,
57
generateIndexSitemap: false,
68
outDir: 'public',
79
exclude: ['/api/*'],
810
alternateRefs: [
911
{
10-
href: 'https://robyn.tech',
12+
href: siteUrl,
1113
hreflang: 'en',
1214
},
1315
{
14-
href: 'https://robyn.tech/zh',
16+
href: `${siteUrl}/zh`,
1517
hreflang: 'zh',
1618
},
1719
],

docs_src/public/site.webmanifest

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,6 @@
77
"background_color": "#000000",
88
"theme_color": "#000000",
99
"icons": [
10-
{
11-
"src": "/favicon-16x16.png",
12-
"sizes": "16x16",
13-
"type": "image/png"
14-
},
15-
{
16-
"src": "/favicon-32x32.png",
17-
"sizes": "32x32",
18-
"type": "image/png"
19-
},
20-
{
21-
"src": "/apple-touch-icon.png",
22-
"sizes": "180x180",
23-
"type": "image/png"
24-
},
2510
{
2611
"src": "/robynog.png",
2712
"sizes": "512x512",

docs_src/src/components/Header.jsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Fragment, useEffect, useRef, useState } from 'react'
1+
import { Fragment, useCallback, useEffect, useRef, useState } from 'react'
22
import Image from 'next/image'
33
import Link from 'next/link'
44
import { useRouter } from 'next/router'
@@ -148,18 +148,18 @@ function GitHubStars() {
148148
const [stars, setStars] = useState('6.1k')
149149
const [loading, setLoading] = useState(false)
150150

151-
const fetchStars = async () => {
151+
const fetchStars = useCallback(async () => {
152152
if (loading) return
153-
153+
154154
setLoading(true)
155155
try {
156156
const response = await fetch('https://api.github.com/repos/sparckles/robyn')
157157
const data = await response.json()
158-
158+
159159
if (data.stargazers_count) {
160160
const count = data.stargazers_count
161-
const formatted = count >= 1000
162-
? `${(count / 1000).toFixed(1)}k`
161+
const formatted = count >= 1000
162+
? `${(count / 1000).toFixed(1)}k`
163163
: count.toString()
164164
setStars(formatted)
165165
}
@@ -168,15 +168,15 @@ function GitHubStars() {
168168
} finally {
169169
setLoading(false)
170170
}
171-
}
171+
}, [loading])
172172

173173
useEffect(() => {
174174
fetchStars()
175-
175+
176176
const interval = setInterval(fetchStars, 300000)
177-
177+
178178
return () => clearInterval(interval)
179-
}, [])
179+
}, [fetchStars])
180180

181181
return (
182182
<a

docs_src/src/components/SEO.jsx

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ const DEFAULT_OG_IMAGE = `${SITE_URL}/robynog.png`
66
const TWITTER_HANDLE = '@robaborobyn'
77
const SITE_NAME = 'Robyn Framework'
88

9+
function escapeJsonLd(obj) {
10+
return JSON.stringify(obj).replace(/</g, '\\u003c').replace(/>/g, '\\u003e')
11+
}
12+
913
export function SEO({
1014
title,
1115
description,
@@ -15,7 +19,8 @@ export function SEO({
1519
jsonLd,
1620
}) {
1721
const router = useRouter()
18-
const canonicalUrl = `${SITE_URL}${router.asPath.split('?')[0]}`
22+
const cleanPath = router.asPath.split(/[?#]/)[0]
23+
const canonicalUrl = `${SITE_URL}${cleanPath}`
1924

2025
const fullTitle = title
2126
? `${title} | ${SITE_NAME}`
@@ -26,8 +31,11 @@ export function SEO({
2631

2732
const metaDescription = description || defaultDescription
2833

29-
const locales = ['en', 'zh']
30-
const currentPath = router.asPath.split('?')[0]
34+
const locales = router.locales || ['en', 'zh']
35+
const defaultLocale = router.defaultLocale || 'en'
36+
37+
const localePrefix = new RegExp(`^/(${locales.join('|')})(/|$)`)
38+
const normalizedPath = cleanPath.replace(localePrefix, '/$2').replace(/^\/\//, '/')
3139

3240
return (
3341
<Head>
@@ -38,18 +46,20 @@ export function SEO({
3846
<link rel="canonical" href={canonicalUrl} />
3947

4048
{locales.map((locale) => {
41-
const localePath =
42-
locale === 'en' ? currentPath : `/${locale}${currentPath}`
49+
const href =
50+
locale === defaultLocale
51+
? `${SITE_URL}${normalizedPath}`
52+
: `${SITE_URL}/${locale}${normalizedPath}`
4353
return (
4454
<link
4555
key={locale}
4656
rel="alternate"
4757
hrefLang={locale}
48-
href={`${SITE_URL}${localePath}`}
58+
href={href}
4959
/>
5060
)
5161
})}
52-
<link rel="alternate" hrefLang="x-default" href={`${SITE_URL}${currentPath}`} />
62+
<link rel="alternate" hrefLang="x-default" href={`${SITE_URL}${normalizedPath}`} />
5363

5464
{/* Open Graph */}
5565
<meta property="og:title" content={fullTitle} />
@@ -71,7 +81,7 @@ export function SEO({
7181
{jsonLd && (
7282
<script
7383
type="application/ld+json"
74-
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
84+
dangerouslySetInnerHTML={{ __html: escapeJsonLd(jsonLd) }}
7585
/>
7686
)}
7787
</Head>

docs_src/src/components/documentation/Layout.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import { Prose } from '@/components/documentation/Prose'
99
import { SectionProvider } from '@/components/documentation/SectionProvider'
1010

1111
function buildBreadcrumbs(path) {
12-
const segments = path.split('/').filter(Boolean)
12+
const cleanPath = path.split(/[?#]/)[0]
13+
const segments = cleanPath.split('/').filter(Boolean)
1314
const items = [{ name: 'Home', href: '/' }]
1415

1516
let currentPath = ''

docs_src/src/lib/formatDate.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
export function formatDate(dateString) {
2-
return new Date(`${dateString}T00:00:00Z`).toLocaleDateString('en-US', {
3-
day: 'numeric',
4-
month: 'long',
2+
return new Date(dateString).toLocaleDateString('en-US', {
53
year: 'numeric',
6-
timeZone: 'UTC',
4+
month: 'long',
5+
day: 'numeric',
76
})
87
}

docs_src/src/lib/getAllBlogPosts.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ export function getAllBlogPosts() {
3333

3434
export function getBlogPostBySlug(slug) {
3535
const filePath = path.join(BLOG_DIR, `${slug}.mdx`)
36+
37+
if (!fs.existsSync(filePath)) {
38+
return null
39+
}
40+
3641
const fileContent = fs.readFileSync(filePath, 'utf-8')
3742
const { data, content } = matter(fileContent)
3843

docs_src/src/pages/404.jsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import Link from 'next/link'
2+
import { useRouter } from 'next/router'
23
import { SEO } from '@/components/SEO'
34
import { Container } from '@/components/Container'
45

56
export default function NotFound() {
7+
const { locale } = useRouter()
8+
const docsHref = `/documentation/${locale || 'en'}`
9+
610
return (
711
<>
812
<SEO title="Page Not Found" description="The page you're looking for doesn't exist." noindex />
@@ -23,7 +27,7 @@ export default function NotFound() {
2327
Go back home
2428
</Link>
2529
<Link
26-
href="/documentation/en"
30+
href={docsHref}
2731
className="text-sm font-semibold text-white"
2832
>
2933
Documentation <span aria-hidden="true"></span>

docs_src/src/pages/_document.jsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,7 @@ export default function Document({ __NEXT_DATA__ }) {
5353
type="application/feed+json"
5454
href={`${process.env.NEXT_PUBLIC_SITE_URL}/rss/feed.json`}
5555
/>
56-
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
57-
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
58-
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
56+
<link rel="icon" type="image/png" href="/robynog.png" />
5957
<link rel="manifest" href="/site.webmanifest" />
6058
<meta name="theme-color" content="#000000" />
6159
</Head>

docs_src/src/pages/blog/[slug].jsx

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,9 @@ import { MDXRemote } from 'next-mdx-remote'
44
import { SEO, BreadcrumbJsonLd } from '@/components/SEO'
55
import { Container } from '@/components/Container'
66
import { getAllBlogPosts, getBlogPostBySlug } from '@/lib/getAllBlogPosts'
7+
import { formatDate } from '@/lib/formatDate'
78
import Link from 'next/link'
89

9-
function formatDate(dateString) {
10-
return new Date(dateString).toLocaleDateString('en-US', {
11-
year: 'numeric',
12-
month: 'long',
13-
day: 'numeric',
14-
})
15-
}
16-
1710
const blogMdxComponents = {
1811
h1: (props) => <h1 className="text-3xl font-bold text-white mt-8 mb-4" {...props} />,
1912
h2: (props) => <h2 className="text-2xl font-bold text-white mt-8 mb-3" {...props} />,
@@ -69,7 +62,7 @@ export default function BlogPost({ post }) {
6962
<li aria-hidden="true">/</li>
7063
<li><Link href="/blog" className="hover:text-gray-300">Blog</Link></li>
7164
<li aria-hidden="true">/</li>
72-
<li className="text-gray-300">{post.title}</li>
65+
<li className="text-gray-300" aria-current="page">{post.title}</li>
7366
</ol>
7467
</nav>
7568

@@ -115,6 +108,11 @@ export async function getStaticPaths() {
115108

116109
export async function getStaticProps({ params }) {
117110
const post = getBlogPostBySlug(params.slug)
111+
112+
if (!post) {
113+
return { notFound: true }
114+
}
115+
118116
const content = await serialize(post.content)
119117

120118
return {

0 commit comments

Comments
 (0)