Guides
Best Practices

Coding Standards

Recommended patterns and practices for building ShellApps.

Project Structure

my-shellapp/
├── app/
│   ├── layout.tsx          # Root layout with providers
│   ├── page.tsx            # Home page
│   ├── providers.tsx       # Client-side providers
│   ├── (auth)/
│   │   ├── login/page.tsx
│   │   └── callback/page.tsx
│   ├── dashboard/
│   │   ├── layout.tsx
│   │   └── page.tsx
│   └── api/
│       └── webhooks/
│           └── route.ts
├── components/
│   ├── ui/                 # App-specific UI components
│   ├── forms/              # Form components
│   └── layouts/            # Layout components
├── lib/
│   ├── api.ts              # API client helpers
│   ├── utils.ts            # Utility functions
│   └── constants.ts        # App constants
├── hooks/                  # Custom React hooks
├── types/                  # TypeScript type definitions
└── public/                 # Static assets

TypeScript Usage

Always use strict TypeScript. Enable strict mode in tsconfig.json:

{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true
  }
}

Type Patterns

// Use interfaces for objects, types for unions/intersections
interface User {
  id: string
  displayName: string
  email: string
  profiles: Profile[]
}
 
type AuthState = 'loading' | 'authenticated' | 'unauthenticated'
 
// Avoid `any` — use `unknown` and narrow
function processInput(input: unknown): string {
  if (typeof input === 'string') return input
  if (typeof input === 'number') return String(input)
  throw new Error('Unexpected input type')
}

Component Patterns with react-ui

Composition over Configuration

import { Card, Button, Stack, Text } from '@shellapps/react-ui'
 
// Good — composable
function FeatureCard({ title, description, onAction }: FeatureCardProps) {
  return (
    <Card>
      <Stack gap="sm">
        <Text as="h3" weight="bold">{title}</Text>
        <Text color="muted">{description}</Text>
        <Button onClick={onAction} variant="primary">
          Learn More
        </Button>
      </Stack>
    </Card>
  )
}

Client vs Server Components

// Server Component (default) — no 'use client' needed
async function UserList() {
  const users = await fetchUsers()
  return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>
}
 
// Client Component — only when you need interactivity
'use client'
function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}

Error Handling

Use ErrorBoundary from react-ui

import { ErrorBoundary } from '@shellapps/react-ui'
 
function App() {
  return (
    <ErrorBoundary
      fallback={({ error, reset }) => (
        <div role="alert">
          <p>Something went wrong: {error.message}</p>
          <button onClick={reset}>Retry</button>
        </div>
      )}
    >
      <Dashboard />
    </ErrorBoundary>
  )
}

API Error Handling

import { ApiError } from '@shellapps/auth-sdk'
 
async function fetchData() {
  try {
    const res = await api.get('/data')
    return res.data
  } catch (error) {
    if (error instanceof ApiError) {
      if (error.status === 401) redirect('/login')
      if (error.status === 429) throw new Error('Rate limited. Try again later.')
    }
    throw error
  }
}

Testing

Unit Tests

__tests__/utils.test.ts
import { describe, it, expect } from 'vitest'
import { formatCurrency } from '@/lib/utils'
 
describe('formatCurrency', () => {
  it('formats USD correctly', () => {
    expect(formatCurrency(1234.5, 'USD')).toBe('$1,234.50')
  })
})

Component Tests

__tests__/auth-buttons.test.tsx
import { render, screen } from '@testing-library/react'
import { AuthButtons } from '@/components/auth-buttons'
 
it('shows login button when not authenticated', () => {
  render(<AuthButtons />)
  expect(screen.getByText('Log In')).toBeInTheDocument()
})

Recommended Setup

npm install -D vitest @testing-library/react @testing-library/jest-dom

Performance Optimization

  • Use Server Components by default — only add 'use client' when needed
  • Lazy-load heavy components with next/dynamic
  • Optimize images with next/image
  • Minimize client-side JavaScript — keep bundles small
  • Use data-t attributes for tracking instead of custom event listeners
import dynamic from 'next/dynamic'
 
const HeavyChart = dynamic(() => import('@/components/chart'), {
  loading: () => <div>Loading chart...</div>,
  ssr: false,
})

Accessibility

  • Use semantic HTML (nav, main, article, button)
  • Ensure all interactive elements are keyboard-accessible
  • Provide aria-label for icon-only buttons
  • Maintain colour contrast ratios (WCAG 2.1 AA minimum)
  • Test with screen readers
// Good
<button aria-label="Close dialog" onClick={onClose}>
  <CloseIcon />
</button>
 
// Good — react-ui components handle a11y by default
<Button variant="primary">Submit</Button>

Next Steps


© 2026 Shell Technology. All rights reserved.