Skip to content
Draft

Rsc #704

Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
refactor: convert dynamic imports to static imports
- Move blog post loading to createServerFn (loadBlogPost)
- Static imports work when server code is in createServerFn
- Route loaders that directly import server modules need dynamic imports
- setCacheHeaders can be static in routes using createServerFn
  • Loading branch information
tannerlinsley committed Feb 11, 2026
commit 68dfe5fa8afb59dfd81b22b8c7b217162b089d7a
44 changes: 7 additions & 37 deletions src/routes/blog.$.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { notFound, redirect, createFileRoute } from '@tanstack/react-router'
import { seo } from '~/utils/seo'
import { PostNotFound } from './blog'
import { formatAuthors } from '~/utils/blog'
import { format } from '~/utils/dates'
import { allPosts } from 'content-collections'
import * as React from 'react'
import { GamHeader } from '~/components/Gam'
Expand All @@ -13,6 +12,7 @@ import { DocTitle } from '~/components/DocTitle'
import { CopyPageDropdown } from '~/components/CopyPageDropdown'
import { Button } from '~/ui'
import { SquarePen } from 'lucide-react'
import { loadBlogPost } from '~/utils/renderBlogContent'

function handleRedirects(docsPath: string) {
if (docsPath.includes('directives-the-new-framework-lock-in')) {
Expand All @@ -25,50 +25,20 @@ function handleRedirects(docsPath: string) {
export const Route = createFileRoute('/blog/$')({
staleTime: Infinity,
loader: async ({ params }) => {
const docsPath = params._splat
if (!docsPath) {
const slug = params._splat
if (!slug) {
throw new Error('Invalid docs path')
}

handleRedirects(docsPath)

const filePath = `src/blog/${docsPath}.md`
const post = allPosts.find((p) => p.slug === docsPath)
handleRedirects(slug)

// Check if post exists before calling server function
const post = allPosts.find((p) => p.slug === slug)
if (!post) {
throw notFound()
}

const { setCacheHeaders } = await import('~/utils/headers.server')
setCacheHeaders()

const now = new Date()
const publishDate = new Date(post.published)
const isUnpublished = post.draft || publishDate > now

const blogContent = `<small>_by ${formatAuthors(post.authors)} on ${format(
new Date(post.published || 0),
'MMMM d, yyyy',
)}._</small>

${post.content}`

const { renderMarkdownToJsx } = await import('~/utils/markdown')
const { headings } = await renderMarkdownToJsx(blogContent)
const { renderBlogContent } = await import('~/utils/renderBlogContent')
const ContentRsc = await renderBlogContent({ data: blogContent })

return {
title: post.title,
description: post.description,
published: post.published,
authors: post.authors,
headerImage: post.headerImage,
filePath,
isUnpublished,
headings,
ContentRsc,
}
return loadBlogPost({ data: { slug } })
},
head: ({ loaderData }) => {
// Generate optimized social media image URL using Netlify Image CDN
Expand Down
2 changes: 1 addition & 1 deletion src/routes/blog.index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Footer } from '~/components/Footer'
import { PostNotFound } from './blog'
import { createServerFn } from '@tanstack/react-start'
import { RssIcon } from 'lucide-react'
import { setCacheHeaders } from '~/utils/headers.server'

type BlogFrontMatter = {
slug: string
Expand All @@ -19,7 +20,6 @@ type BlogFrontMatter = {

const fetchFrontMatters = createServerFn({ method: 'GET' }).handler(
async () => {
const { setCacheHeaders } = await import('~/utils/headers.server')
setCacheHeaders()

const posts = getPublishedPosts()
Expand Down
2 changes: 1 addition & 1 deletion src/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { formatAuthors, getPublishedPosts } from '~/utils/blog'
import { format } from '~/utils/dates'
import { SimpleMarkdown } from '~/components/SimpleMarkdown'
import { renderMarkdownAsync } from '~/utils/markdown'
import { setCacheHeaders } from '~/utils/headers.server'
import { NetlifyImage } from '~/components/NetlifyImage'
import { createServerFn } from '@tanstack/react-start'
import { AdGate } from '~/contexts/AdsContext'
Expand Down Expand Up @@ -63,7 +64,6 @@ type BlogFrontMatter = {

const fetchRecentPosts = createServerFn({ method: 'GET' }).handler(
async (): Promise<BlogFrontMatter[]> => {
const { setCacheHeaders } = await import('~/utils/headers.server')
setCacheHeaders()

const posts = getPublishedPosts().slice(0, 3)
Expand Down
50 changes: 45 additions & 5 deletions src/utils/renderBlogContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,50 @@ import { createServerFn } from '@tanstack/react-start'
import { renderServerComponent } from '@tanstack/react-start/rsc'
import { renderMarkdownToJsx } from '~/utils/markdown'
import { BlogContent } from '~/components/markdown/BlogContent'
import { setCacheHeaders } from '~/utils/headers.server'
import { allPosts } from 'content-collections'
import { formatAuthors } from '~/utils/blog'
import { format } from '~/utils/dates'
import { notFound } from '@tanstack/react-router'
import * as v from 'valibot'

export const renderBlogContent = createServerFn({ method: 'GET' })
.inputValidator((content: string) => content)
.handler(async ({ data: content }) => {
const { content: jsxContent } = await renderMarkdownToJsx(content)
return renderServerComponent(<BlogContent>{jsxContent}</BlogContent>)
export const loadBlogPost = createServerFn({ method: 'GET' })
.inputValidator(v.object({ slug: v.string() }))
.handler(async ({ data: { slug } }) => {
const post = allPosts.find((p) => p.slug === slug)

if (!post) {
throw notFound()
}

setCacheHeaders()

const now = new Date()
const publishDate = new Date(post.published)
const isUnpublished = post.draft || publishDate > now

const blogContent = `<small>_by ${formatAuthors(post.authors)} on ${format(
new Date(post.published || 0),
'MMMM d, yyyy',
)}._</small>

${post.content}`

const { content: jsxContent, headings } =
await renderMarkdownToJsx(blogContent)
const ContentRsc = await renderServerComponent(
<BlogContent>{jsxContent}</BlogContent>,
)

return {
title: post.title,
description: post.description,
published: post.published,
authors: post.authors,
headerImage: post.headerImage,
filePath: `src/blog/${slug}.md`,
isUnpublished,
headings,
ContentRsc,
}
})
Loading