Skip to content

Commit a217302

Browse files
committed
fix loading performance
1 parent ebe991e commit a217302

File tree

9 files changed

+134
-74
lines changed

9 files changed

+134
-74
lines changed

.claude/settings.local.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"Bash(grep:*)",
1616
"Bash(rg:*)",
1717
"Bash(git stash:*)",
18-
"WebFetch(domain:docs.anthropic.com)"
18+
"WebFetch(domain:docs.anthropic.com)",
19+
"WebFetch(domain:tailwindcss.com)"
1920
],
2021
"deny": []
2122
},

app/__tests__/routes/blog.post.test.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,9 @@ describe('BlogPost Route', () => {
9696
})
9797

9898
describe('loader function', () => {
99-
it('should return post data for existing post', async () => {
99+
it('should return post data for existing post', () => {
100100
const result = loader({ params: { slug: 'test-post' } } as any)
101-
expect(result).toBeInstanceOf(Response)
102-
const data = await result.json()
103-
expect(data).toEqual({ post: mockPost })
101+
expect(result).toEqual({ post: mockPost })
104102
})
105103

106104
it('should throw 404 for non-existing post', () => {

app/__tests__/routes/blog.test.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,9 @@ describe('Blog Route', () => {
103103
})
104104

105105
describe('loader function', () => {
106-
it('should return posts data with cache config', async () => {
106+
it('should return posts data', () => {
107107
const result = loader()
108-
expect(result).toBeInstanceOf(Response)
109-
const data = await result.json()
110-
expect(data).toEqual({
108+
expect(result).toEqual({
111109
posts: [
112110
{
113111
slug: 'test-post-1',

app/components/MarkdownContent.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ const markdownStyles = {
2525
links:
2626
'[&_a]:text-blue-600 [&_a]:dark:text-blue-400 [&_a]:underline [&_a]:underline-offset-2 [&_a]:decoration-1 [&_a]:hover:text-blue-800 [&_a]:hover:dark:text-blue-300 [&_a]:transition-colors',
2727
headings: {
28-
h1: '[&_h1]:text-4xl [&_h1]:font-bold [&_h1]:mb-6 [&_h1]:mt-10',
29-
h2: '[&_h2]:text-3xl [&_h2]:font-semibold [&_h2]:mb-5 [&_h2]:mt-8',
30-
h3: '[&_h3]:text-2xl [&_h3]:font-semibold [&_h3]:mb-4 [&_h3]:mt-6',
31-
h4: '[&_h4]:text-xl [&_h4]:font-semibold [&_h4]:mb-3 [&_h4]:mt-4',
32-
h5: '[&_h5]:text-lg [&_h5]:font-semibold [&_h5]:mb-2 [&_h5]:mt-3',
33-
h6: '[&_h6]:text-base [&_h6]:font-semibold [&_h6]:mb-2 [&_h6]:mt-3',
28+
h1: '[&_h1]:text-4xl [&_h1]:font-bold [&_h1]:mb-6 [&_h1]:mt-10 [&_h1]:[font-size:2.25rem]',
29+
h2: '[&_h2]:text-3xl [&_h2]:font-semibold [&_h2]:mb-5 [&_h2]:mt-8 [&_h2]:[font-size:1.875rem]',
30+
h3: '[&_h3]:text-2xl [&_h3]:font-semibold [&_h3]:mb-4 [&_h3]:mt-6 [&_h3]:[font-size:1.5rem]',
31+
h4: '[&_h4]:text-xl [&_h4]:font-semibold [&_h4]:mb-3 [&_h4]:mt-4 [&_h4]:[font-size:1.25rem]',
32+
h5: '[&_h5]:text-lg [&_h5]:font-semibold [&_h5]:mb-2 [&_h5]:mt-3 [&_h5]:[font-size:1.125rem]',
33+
h6: '[&_h6]:text-base [&_h6]:font-semibold [&_h6]:mb-2 [&_h6]:mt-3 [&_h6]:[font-size:1rem]',
3434
},
3535
lists: {
3636
ul: '[&_ul]:list-disc [&_ul]:pl-6 [&_ul]:mb-6',

app/components/ShareButtons/icons/LinkIcon.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ export function LinkIcon(props: SVGProps<SVGSVGElement>) {
1717
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
1818
</svg>
1919
)
20-
}
20+
}

app/routes/blog.post.tsx

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,23 @@ import { MarkdownContent } from '~/components/MarkdownContent'
77
import { ShareButtons } from '~/components/ShareButtons'
88
import { RSSIcon } from '~/components/icons'
99

10+
export const links: Route.LinksFunction = () => [
11+
{
12+
rel: 'preload',
13+
href: '/fonts/Inter_18pt-Regular.woff2',
14+
as: 'font',
15+
type: 'font/woff2',
16+
crossOrigin: 'anonymous',
17+
},
18+
{
19+
rel: 'preload',
20+
href: '/fonts/Inter_18pt-Bold.woff2',
21+
as: 'font',
22+
type: 'font/woff2',
23+
crossOrigin: 'anonymous',
24+
},
25+
]
26+
1027
export function meta({ params }: Route.MetaArgs) {
1128
const post = getPostBySlug(params.slug)
1229
if (!post) {
@@ -74,16 +91,18 @@ export function loader({ params }: Route.LoaderArgs) {
7491
throw new Response('Not Found', { status: 404 })
7592
}
7693

77-
return new Response(JSON.stringify({ post }), {
78-
headers: {
79-
'Content-Type': 'application/json',
80-
'Cache-Control': 'public, max-age=7200, s-maxage=604800, stale-while-revalidate=86400',
81-
'CDN-Cache-Control': 'max-age=604800',
82-
'Cloudflare-CDN-Cache-Control': 'max-age=604800',
83-
ETag: `"blog-post-${params.slug}-${post.date}"`,
84-
Vary: 'Accept-Encoding',
85-
},
86-
})
94+
return {
95+
post,
96+
}
97+
}
98+
99+
export function headers() {
100+
return {
101+
'Cache-Control': 'public, max-age=7200, s-maxage=604800, stale-while-revalidate=86400',
102+
'CDN-Cache-Control': 'max-age=604800',
103+
'Cloudflare-CDN-Cache-Control': 'max-age=604800',
104+
Vary: 'Accept-Encoding',
105+
}
87106
}
88107

89108
export default function BlogPost({ loaderData }: Route.ComponentProps) {
@@ -119,7 +138,12 @@ export default function BlogPost({ loaderData }: Route.ComponentProps) {
119138

120139
<article className="prose prose-gray dark:prose-invert max-w-none">
121140
<header className="mb-8">
122-
<h1 className="text-4xl font-bold text-gray-900 dark:text-white mb-4">{post.title}</h1>
141+
<h1
142+
className="text-4xl font-bold text-gray-900 dark:text-white mb-4"
143+
style={{ fontSize: '2.25rem' }}
144+
>
145+
{post.title}
146+
</h1>
123147
<div className="flex items-center text-gray-700 dark:text-gray-200 mb-4 space-x-2">
124148
<span>
125149
{new Date(post.date).toLocaleDateString('en-US', {

app/routes/blog.tsx

Lines changed: 60 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,23 @@ import { Footer } from '~/components/Footer'
66
import { RSSIcon } from '~/components/icons'
77
import { memo } from 'react'
88

9+
export const links: Route.LinksFunction = () => [
10+
{
11+
rel: 'preload',
12+
href: '/fonts/Inter_18pt-Regular.woff2',
13+
as: 'font',
14+
type: 'font/woff2',
15+
crossOrigin: 'anonymous',
16+
},
17+
{
18+
rel: 'preload',
19+
href: '/fonts/Inter_18pt-Bold.woff2',
20+
as: 'font',
21+
type: 'font/woff2',
22+
crossOrigin: 'anonymous',
23+
},
24+
]
25+
926
export function meta(_args: Route.MetaArgs) {
1027
const url = 'https://mawburn.com/blog'
1128
const description =
@@ -36,56 +53,53 @@ export function meta(_args: Route.MetaArgs) {
3653
export function loader() {
3754
const posts = getAllPostsMetadata()
3855

39-
return new Response(JSON.stringify({ posts }), {
40-
headers: {
41-
'Content-Type': 'application/json',
42-
'Cache-Control': 'public, max-age=3600, s-maxage=86400, stale-while-revalidate=86400',
43-
'CDN-Cache-Control': 'max-age=86400',
44-
'Cloudflare-CDN-Cache-Control': 'max-age=86400',
45-
ETag: `"blog-list-${posts.length}"`,
46-
Vary: 'Accept-Encoding',
47-
},
48-
})
56+
return {
57+
posts,
58+
}
4959
}
5060

51-
const BlogPostCard = memo(({ post }: { post: BlogPostMetadata }) => {
52-
const formattedDate = new Date(post.date).toLocaleDateString('en-US', {
53-
year: 'numeric',
54-
month: 'long',
55-
day: 'numeric',
56-
})
61+
export function headers() {
62+
return {
63+
'Cache-Control': 'public, max-age=3600, s-maxage=86400, stale-while-revalidate=86400',
64+
'CDN-Cache-Control': 'max-age=86400',
65+
'Cloudflare-CDN-Cache-Control': 'max-age=86400',
66+
Vary: 'Accept-Encoding',
67+
}
68+
}
5769

58-
return (
59-
<article
60-
key={post.slug}
61-
className="border-b border-gray-200 dark:border-gray-700 pb-8 mb-8 last:mb-0"
70+
const BlogPostCard = memo(({ post }: { post: BlogPostMetadata }) => (
71+
<article className="border-b border-gray-200 dark:border-gray-700 pb-8 mb-8 last:mb-0">
72+
<Link
73+
to={`/blog/${post.slug}`}
74+
className="block hover:bg-gray-50 dark:hover:bg-gray-800 rounded-lg p-4 -m-4 transition-colors"
75+
prefetch="intent"
6276
>
63-
<Link
64-
to={`/blog/${post.slug}`}
65-
className="block hover:bg-gray-50 dark:hover:bg-gray-800 rounded-lg p-4 -m-4 transition-colors"
66-
prefetch="intent"
67-
>
68-
<h2 className="text-2xl font-semibold text-gray-900 dark:text-white mb-2 hover:text-fuchsia-600 dark:hover:text-fuchsia-400 transition-colors">
69-
{post.title}
70-
</h2>
71-
<div className="flex items-center text-sm text-gray-700 dark:text-gray-200 mb-3 space-x-2">
72-
<time dateTime={post.date}>{formattedDate}</time>
73-
<span></span>
74-
<span>{post.readTime} min read</span>
75-
</div>
76-
<p className="text-gray-600 dark:text-gray-300 mb-3">{post.excerpt}</p>
77-
<div className="flex flex-wrap gap-2">
78-
<span className="inline-block font-bold text-gray-700 dark:text-gray-200 text-xs whitespace-nowrap">
79-
Tags:
80-
</span>{' '}
81-
<span className="inline-block text-gray-700 dark:text-gray-400 text-xs whitespace-nowrap">
82-
{post.tags.join(', ')}
83-
</span>
84-
</div>
85-
</Link>
86-
</article>
87-
)
88-
})
77+
<h2 className="text-2xl font-semibold text-gray-900 dark:text-white mb-2 hover:text-fuchsia-600 dark:hover:text-fuchsia-400 transition-colors">
78+
{post.title}
79+
</h2>
80+
<div className="flex items-center text-sm text-gray-700 dark:text-gray-200 mb-3 space-x-2">
81+
<time dateTime={post.date}>
82+
{new Date(post.date).toLocaleDateString('en-US', {
83+
year: 'numeric',
84+
month: 'long',
85+
day: 'numeric',
86+
})}
87+
</time>
88+
<span></span>
89+
<span>{post.readTime} min read</span>
90+
</div>
91+
<p className="text-gray-600 dark:text-gray-300 mb-3">{post.excerpt}</p>
92+
<div className="flex flex-wrap gap-2">
93+
<span className="inline-block font-bold text-gray-700 dark:text-gray-200 text-xs whitespace-nowrap">
94+
Tags:
95+
</span>{' '}
96+
<span className="inline-block text-gray-700 dark:text-gray-400 text-xs whitespace-nowrap">
97+
{post.tags.join(', ')}
98+
</span>
99+
</div>
100+
</Link>
101+
</article>
102+
))
89103

90104
BlogPostCard.displayName = 'BlogPostCard'
91105

app/utils/markdown.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const md = new MarkdownIt({
88

99
const defaultRender =
1010
md.renderer.rules.link_open ||
11-
function (tokens, idx, options, env, renderer) {
11+
function (tokens, idx, options, _env, renderer) {
1212
return renderer.renderToken(tokens, idx, options)
1313
}
1414

vite.config.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export default defineConfig({
1414
build: {
1515
minify: 'terser',
1616
cssMinify: true,
17+
cssCodeSplit: true,
1718
terserOptions: {
1819
compress: {
1920
drop_console: true,
@@ -36,11 +37,35 @@ export default defineConfig({
3637
}
3738
return 'vendor'
3839
}
40+
// Route-based splitting for better caching
41+
if (id.includes('routes/blog.tsx')) {
42+
return 'blog-list'
43+
}
44+
if (id.includes('routes/blog.post.')) {
45+
return 'blog-post'
46+
}
47+
if (id.includes('routes/home.')) {
48+
return 'home'
49+
}
3950
// Separate Three.js background into its own chunk
4051
if (id.includes('SynthwaveBackground')) {
4152
return 'synthwave'
4253
}
4354
},
55+
assetFileNames: assetInfo => {
56+
if (!assetInfo.names?.[0]) return `assets/[name]-[hash].[ext]`
57+
const name = assetInfo.names[0]
58+
const info = name.split('.')
59+
const ext = info[info.length - 1]
60+
if (/\.(css)$/.test(name)) {
61+
// Route-specific CSS naming for better caching
62+
if (name.includes('blog')) {
63+
return `css/blog-[hash].${ext}`
64+
}
65+
return `css/[name]-[hash].${ext}`
66+
}
67+
return `assets/[name]-[hash].${ext}`
68+
},
4469
},
4570
},
4671
},

0 commit comments

Comments
 (0)