| name | coding-standards |
| description | TypeScript and JavaScript coding standards for the personal blog project |
Coding Standards
This skill defines the coding standards and best practices for TypeScript/JavaScript in the personal blog project.
TypeScript Configuration
Strict Mode (Required)
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true
}
}
Type Safety Rules
function getPosts(): Promise<NotionPost[]> {
}
function getData() {
}
function isNotionPost(obj: unknown): obj is NotionPost {
return (
typeof obj === 'object' &&
obj !== null &&
'id' in obj &&
'title' in obj
);
}
const post = data as NotionPost;
Code Organization
File Structure
src/
├── app/ # Next.js pages (App Router)
├── components/ # Reusable React components
├── lib/ # Utilities and helpers
│ ├── notion.ts # Notion API client
│ ├── utils.ts # General utilities
│ └── types.ts # Shared types
└── styles/ # Global styles (if any)
File Size Limits
- Components: Max 200 lines
- Pages: Max 150 lines
- Utilities: Max 100 lines
- Types: Max 50 lines per file
If exceeding limits, split into smaller modules.
Single Responsibility Principle
export async function getPosts() { }
export async function getPostBySlug() { }
export function formatDate() { }
export function formatTags() { }
export function getPosts() { }
export function formatDate() { }
export function validateEmail() { }
Naming Conventions
Files and Directories
✅ Components: PostCard.tsx, Header.tsx
✅ Utilities: notion.ts, utils.ts, formatters.ts
✅ Types: types.ts, notion-types.ts
✅ Tests: PostCard.test.tsx, notion.test.ts
✅ Directories: components/, lib/, app/
Variables and Functions
const postCount = 10;
function getPostBySlug(slug: string) { }
function PostCard() { }
interface NotionPost { }
type PostStatus = 'draft' | 'published';
const MAX_POSTS_PER_PAGE = 10;
const API_BASE_URL = 'https://api.notion.com';
const publishedPosts = posts.filter(p => p.published);
const psts = getPsts();
Functional Programming
Immutability
const newPosts = [...posts, newPost];
const updatedPost = { ...post, title: 'New Title' };
posts.push(newPost);
post.title = 'New Title';
const publishedPosts = posts.filter(p => p.published);
const titles = posts.map(p => p.title);
const publishedPosts = [];
for (let i = 0; i < posts.length; i++) {
if (posts[i].published) {
publishedPosts.push(posts[i]);
}
}
Pure Functions
function formatDate(date: string): string {
return new Date(date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
}
let formattedDate = '';
function formatDate(date: string): void {
formattedDate = new Date(date).toLocaleDateString();
}
Function Composition
const getPublishedPosts = (posts: NotionPost[]) =>
posts.filter(p => p.published);
const sortByDate = (posts: NotionPost[]) =>
posts.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
const getRecentPublishedPosts = (posts: NotionPost[]) =>
sortByDate(getPublishedPosts(posts)).slice(0, 5);
Error Handling
Explicit Error Handling
async function getPosts(): Promise<NotionPost[]> {
try {
const response = await notion.databases.query({
database_id: process.env.NOTION_DATABASE_ID!,
});
return response.results.map(mapNotionToPost);
} catch (error) {
console.error('Failed to fetch posts:', error);
return [];
}
}
async function getPosts(): Promise<NotionPost[]> {
const response = await notion.databases.query({
database_id: process.env.NOTION_DATABASE_ID!,
});
return response.results.map(mapNotionToPost);
}
Type-Safe Error Handling
class NotionError extends Error {
constructor(message: string, public code: string) {
super(message);
this.name = 'NotionError';
}
}
function handleNotionError(error: unknown): void {
if (error instanceof NotionError) {
console.error(`Notion API error [${error.code}]:`, error.message);
} else if (error instanceof Error) {
console.error('Unexpected error:', error.message);
} else {
console.error('Unknown error:', error);
}
}
Async/Await
Prefer async/await over Promises
async function getPostWithContent(slug: string) {
const post = await getPostBySlug(slug);
const content = await getPageContent(post.id);
return { post, content };
}
function getPostWithContent(slug: string) {
return getPostBySlug(slug)
.then(post => getPageContent(post.id)
.then(content => ({ post, content }))
);
}
Parallel Async Operations
async function getPostsAndTags() {
const [posts, tags] = await Promise.all([
getPosts(),
getTags(),
]);
return { posts, tags };
}
async function getPostsAndTags() {
const posts = await getPosts();
const tags = await getTags();
return { posts, tags };
}
Comments and Documentation
When to Comment
export const revalidate = 3600;
function mapNotionToPost(page: NotionPage): NotionPost {
}
const posts = await getPosts();
JSDoc for Public APIs
export async function getPosts(): Promise<NotionPost[]> {
}
Import Organization
import { Client } from '@notionhq/client';
import { notFound } from 'next/navigation';
import { getPosts } from '@/lib/notion';
import { formatDate } from '@/lib/utils';
import PostCard from '@/components/PostCard';
import type { NotionPost } from '@/lib/types';
import { helper } from './helper';
React Component Standards
Function Components Only
export default function PostCard({ post }: { post: NotionPost }) {
return <article>{post.title}</article>;
}
export default class PostCard extends React.Component {
render() {
return <article>{this.props.post.title}</article>;
}
}
Props Destructuring
function PostCard({ post, showSummary = true }: PostCardProps) {
return <article>{post.title}</article>;
}
function PostCard(props: PostCardProps) {
return <article>{props.post.title}</article>;
}
Component Organization
import { formatDate } from '@/lib/utils';
interface PostCardProps {
post: NotionPost;
showSummary?: boolean;
}
export default function PostCard({ post, showSummary = true }: PostCardProps) {
const [isExpanded, setIsExpanded] = useState(false);
const formattedDate = formatDate(post.date);
const handleClick = () => setIsExpanded(!isExpanded);
return (
<article onClick={handleClick}>
<h2>{post.title}</h2>
<time>{formattedDate}</time>
{showSummary && <p>{post.summary}</p>}
</article>
);
}
function formatPostTitle(title: string): string {
return title.toUpperCase();
}
ESLint and Prettier
Required Rules
{
"extends": ["next/core-web-vitals", "next/typescript"],
"rules": {
"no-console": ["warn", { "allow": ["error", "warn"] }],
"prefer-const": "error",
"no-var": "error",
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-unused-vars": "error"
}
}
Code Formatting
- Use Prettier for consistent formatting
- 2 spaces for indentation
- Single quotes for strings
- Semicolons required
- Trailing commas in multi-line
Performance Considerations
Avoid Unnecessary Re-renders
const sortedPosts = useMemo(
() => posts.sort((a, b) => b.date.localeCompare(a.date)),
[posts]
);
const handleClick = useCallback(() => {
setIsExpanded(!isExpanded);
}, [isExpanded]);
Lazy Loading
const HeavyChart = dynamic(() => import('@/components/HeavyChart'), {
loading: () => <p>Loading chart...</p>,
ssr: false,
});
Code Review Checklist
Before submitting code: