-
Notifications
You must be signed in to change notification settings - Fork 0
blog: support Markdown rendering and 404 routes #38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f6c3023
1f342c6
3bcd1a1
6264559
3665e84
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import fs from 'fs'; | ||
| import path from 'path'; | ||
|
|
||
| const postsDir = './public/posts'; | ||
| const distDir = './dist'; | ||
|
|
||
| const years = fs.readdirSync(postsDir); | ||
|
|
||
| years.forEach(year => { | ||
| const yearPath = path.join(postsDir, year); | ||
| if (fs.lstatSync(yearPath).isDirectory()) { | ||
| const files = fs.readdirSync(yearPath); | ||
| files.forEach(file => { | ||
| if (file.endsWith('.md')) { | ||
| const slug = file.replace('.md', ''); | ||
| const targetDir = path.join(distDir, year); | ||
|
|
||
| if (!fs.existsSync(targetDir)) { | ||
| fs.mkdirSync(targetDir, { | ||
| recursive: true | ||
| }); | ||
| } | ||
| fs.copyFileSync(path.join(distDir, 'index.html'), path.join(targetDir, `${slug}.html`)); | ||
| } | ||
| }); | ||
| } | ||
| }); | ||
|
|
||
| fs.copyFileSync( | ||
| path.join(distDir, 'index.html'), | ||
| path.join(distDir, '404.html') | ||
| ); | ||
|
|
||
| console.log('✅ Build HTML from Markdown Post'); |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| # Hello React 19 | ||
|
|
||
| This is a post read from `public/posts/2026/Test.md`. | ||
|
|
||
| ## Testing Functionality | ||
|
|
||
| * **Automatic Path**: Accessed via `/2026/Test.html` | ||
|
|
||
| * **GA Tracing**: `Analytics.jsx` is enabled | ||
|
|
||
| * **Environment**: Driven by **Bun** | ||
|
|
||
| ### Code Example | ||
| ```javascript | ||
| console.log("Hello from 2026!"); | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,12 +1,62 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| import { useState, useEffect } from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||
| import ReactMarkdown from 'react-markdown'; | ||||||||||||||||||||||||||||||||||||||||||||||
| import Analytics from './Analytics'; | ||||||||||||||||||||||||||||||||||||||||||||||
| import NotFound from './NotFound'; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const allPostFiles = import.meta.glob('/public/posts/**/*.md', { query: '?url', import: 'default' }); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| function App() { | ||||||||||||||||||||||||||||||||||||||||||||||
| const [content, setContent] = useState(''); | ||||||||||||||||||||||||||||||||||||||||||||||
| const [status, setStatus] = useState('loading'); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||
| const params = new URLSearchParams(window.location.search); | ||||||||||||||||||||||||||||||||||||||||||||||
| const redirectedPath = params.get('p'); | ||||||||||||||||||||||||||||||||||||||||||||||
| const currentPath = redirectedPath || window.location.pathname; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if (redirectedPath) { | ||||||||||||||||||||||||||||||||||||||||||||||
| window.history.replaceState(null, '', redirectedPath); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if (currentPath === '/' || currentPath === '/index.html') { | ||||||||||||||||||||||||||||||||||||||||||||||
| setContent('# Welcome My Blog'); | ||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||||||||||||||
| setStatus('success'); | ||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const parts = currentPath.replace(/\.html$/, '').split('/').filter(Boolean); | ||||||||||||||||||||||||||||||||||||||||||||||
| const [year, slug] = parts; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if (year && slug) { | ||||||||||||||||||||||||||||||||||||||||||||||
| const expectedPath = `/public/posts/${year}/${slug}.md`; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if (allPostFiles[expectedPath]) { | ||||||||||||||||||||||||||||||||||||||||||||||
| fetch(`/posts/${year}/${slug}.md`) | ||||||||||||||||||||||||||||||||||||||||||||||
| .then(res => res.text()) | ||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The For example: .then(res => {
if (!res.ok) {
throw new Error(`HTTP error! status: ${res.status}`);
}
return res.text();
}) |
||||||||||||||||||||||||||||||||||||||||||||||
| .then(text => { | ||||||||||||||||||||||||||||||||||||||||||||||
| setContent(text); | ||||||||||||||||||||||||||||||||||||||||||||||
| setStatus('success'); | ||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||
| .catch(() => setStatus('404')); | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+34
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛑 Logic Error: The fetch error handler catches all errors and displays a 404 page, which will mislead users when network errors or server errors occur. Users experiencing connection issues will see "Page Not Found" instead of an appropriate error message.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||
| setStatus('404'); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||
| setStatus('404'); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| }, []); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if (status === 'loading') return <div>Loading...</div>; | ||||||||||||||||||||||||||||||||||||||||||||||
| if (status === '404') return <NotFound />; | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+49
to
+50
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛑 Logic Error: Missing status condition handler. The code sets status to 'error' for network failures but only checks for 'loading' and '404' states, causing the component to render nothing when network errors occur.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| function App() { | ||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||
| <Analytics /> | ||||||||||||||||||||||||||||||||||||||||||||||
| <h1 className="main-title">Hello World</h1> | ||||||||||||||||||||||||||||||||||||||||||||||
| <article style={{ padding: '40px', maxWidth: '800px', margin: '0 auto' }}> | ||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||||||||||||||
| <ReactMarkdown>{content}</ReactMarkdown> | ||||||||||||||||||||||||||||||||||||||||||||||
| </article> | ||||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+8
to
+60
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This component currently handles routing, data fetching, and rendering. This can make it harder to maintain. Consider these refactors:
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| export default App; | ||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import Analytics from './Analytics'; | ||
|
|
||
| export default function NotFound() { | ||
| return ( | ||
| <> | ||
| <Analytics /> | ||
| <div style={{ textAlign: 'center', padding: '10vh 20px' }}> | ||
| <h1 style={{ fontSize: '3rem', color: '#ff4d4f' }}>404</h1> | ||
| <h2>Page Not Found</h2> | ||
| <p>Sorry, the article or page you are looking for seems to have moved or no longer exists.</p> | ||
| <a href="/" style={{ color: '#1890ff', textDecoration: 'none' }}> | ||
| ← Return to homepage | ||
| </a> | ||
| </div> | ||
|
Comment on lines
+7
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| </> | ||
| ); | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a good practice for text files to end with a newline character. Some tools and systems expect this and may behave unexpectedly without it.