Skip to content

Phase 0 - Foundation Decisions

Status: Done

  • Track: Both (MVP & Showcase)
  • Effort: Minimal, foundational setup
  • Dependencies: None
  • Deliverables: Core architecture decisions, repository setup, development environment
  • Project requirements defined
  • Hosting platform chosen
  • Team assembled (if applicable)
  • Development machines ready
StepTaskMVPShowcaseNotes
0.01Initialize repositoryInclude .gitignore, README
0.02Choose package managerpnpm recommended for speed
0.03Set up Node.js versionUse .nvmrc pinned to the version in the starter’s .nvmrc (Node 24 line)
0.04Select framework versionLatest stable Astro major (see the starter’s versions.json)
0.05Configure TypeScriptStrict mode from start
0.05aConfigure Biome (lint/format)Init @biomejs/biome & VSCode extension
0.06Initialize Astro projectUse create-astro CLI
0.07Set up Git hooksHusky + lint-staged
0.08Create branch strategyDocument in README
0.09Configure environment vars.env.example template
0.10Create ADR structuredocs/adr/template.md
0.11Document key decisionsFirst ADR entry
Terminal window
# Create project with pnpm
pnpm create astro@latest my-portfolio -- \
--template minimal \
--typescript strict \
--git \
--no-install
cd my-portfolio
pnpm install
package.json
{
"packageManager": "pnpm@10.13.1",
"engines": {
"node": ">=24.0.0",
"pnpm": ">=10.0.0"
},
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"check": "SITE_URL=${SITE_URL:-http://localhost:4321} astro check",
"check:types": "tsc --noEmit",
"format": "biome format . --write",
"lint": "biome check ."
}
}

Astro and TypeScript checking are split into separate scripts (check and check:types), and lint is check-only — autofixing happens via lint-staged in the pre-commit hook, not in the lint script itself.

biome.json
{
"$schema": "https://biomejs.dev/schemas/2.4.9/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"includes": [
"src/**/*.{astro,ts,tsx,js,jsx,md,mdx}",
"docs/**/*.{md,mdx}",
"scripts/**/*.{ts,js,mjs}",
"*.{md,mdx,ts,js,mjs}"
]
},
"formatter": {
"enabled": true,
"formatWithErrors": true,
"indentStyle": "space",
"indentWidth": 2,
"lineEnding": "lf",
"lineWidth": 100,
"attributePosition": "auto"
},
"javascript": {
"formatter": {
"jsxQuoteStyle": "double",
"quoteProperties": "asNeeded",
"trailingCommas": "all",
"semicolons": "always",
"arrowParentheses": "always",
"bracketSpacing": true,
"bracketSameLine": false,
"quoteStyle": "double"
}
},
"css": {
"formatter": {
"enabled": true
}
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"complexity": {
"noBannedTypes": "error",
"noUselessTypeConstraint": "error"
},
"correctness": {
"noUnusedVariables": "error",
"useExhaustiveDependencies": "error"
},
"suspicious": {
"noExplicitAny": "error",
"noImplicitAnyLet": "error"
},
"a11y": {
"recommended": true
},
"style": {
"noNonNullAssertion": "warn",
"useAsConstAssertion": "warn",
"useBlockStatements": "warn",
"noParameterAssign": "warn",
"useDefaultParameterLast": "warn",
"useEnumInitializers": "warn",
"useSelfClosingElements": "warn",
"useSingleVarDeclarator": "warn",
"useNumberNamespace": "warn",
"noInferrableTypes": "warn",
"noUselessElse": "warn"
}
}
}
}

The project config also defines a useNamingConvention rule and an overrides array (per-glob rules for Markdown, scripts, Astro, CSS, tests, and config files). See the repo's biome.json for the authoritative, complete configuration.

root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
Terminal window
# .nvmrc — pin the Node major the project targets (copy the exact
# value from the starter's .nvmrc; the starter requires Node >=24)
24
tsconfig.json
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@layouts/*": ["src/layouts/*"],
"@utils/*": ["src/utils/*"],
"@styles/*": ["src/styles/*"],
"@types/*": ["src/types/*"],
"@content/*": ["src/content/*"],
"@assets/*": ["src/assets/*"],
"@scripts/*": ["scripts/*"]
}
}
}
Terminal window
# Install Husky
pnpm add -D husky lint-staged
pnpm exec husky init
# Create pre-commit hook
echo 'pnpm exec lint-staged' > .husky/pre-commit

.husky/pre-commit:

Terminal window
pnpm exec lint-staged

.husky/commit-msg:

Terminal window
pnpm exec commitlint --edit "$1"

.husky/pre-push:

#!/usr/bin/env sh
# Run the unit test suite before pushing so failures surface locally
# instead of in CI. Skip with --no-verify if you have a deliberate reason
# (e.g. pushing WIP for backup).
pnpm run test:unit

Husky 9 hook files are plain shell commands — the husky.sh sourcing boilerplate from Husky 8 is gone.

// package.json - lint-staged configuration
{
"lint-staged": {
"*.{astro,ts,tsx,js,jsx,json,yml,yaml}": "biome check --write --no-errors-on-unmatched",
"*.{md,mdx}": "markdownlint-cli2 --fix"
}
}
Terminal window
# Environment Variables Template
# Copy this file to .env and fill in your values
# DO NOT commit .env to version control
# Site Configuration
#
# SITE_URL (required for production builds)
# The canonical origin URL where your site will be hosted. No trailing slash.
# The build will fail if this is not set or contains placeholder values.
# For GitHub Pages: https://<your-github-username>.github.io
# For custom domains: https://your-domain.com
# SITE_URL=https://your-username.github.io
#
# Alternatively, use PUBLIC_SITE_URL (exposed to client-side code via Astro):
PUBLIC_SITE_URL=http://localhost:4321
# Deployment Target
# Set to "gh-pages" when deploying to GitHub Pages.
# This derives the base path from the package.json "name" field automatically.
# Leave unset for root deployments (Cloudflare Pages, Netlify, Vercel, etc.)
# DEPLOY_TARGET=gh-pages
# Analytics (optional) - See docs/implementation-guides/06-optional-features/01-analytics.md
# Not yet implemented — uncomment and set when adding analytics support.
# PUBLIC_PLAUSIBLE_DOMAIN="your-domain.com"
# PUBLIC_FATHOM_SITE_ID="YOUR_FATHOM_SITE_ID"
# Contact Information
PUBLIC_CONTACT_EMAIL=hello@example.com
PUBLIC_CONTACT_PHONE=+1234567890
PUBLIC_CONTACT_PHONE_DISPLAY="+1 (234) 567-890"
PUBLIC_CONTACT_LOCATION="San Francisco, CA"
PUBLIC_CONTACT_TIMEZONE="Mon-Fri, 9AM-6PM PST"
# Social Media Links
PUBLIC_SOCIAL_GITHUB=https://github.com/example
PUBLIC_SOCIAL_LINKEDIN=https://linkedin.com/company/example
PUBLIC_SOCIAL_TWITTER=https://twitter.com/example

Note: The starter's typed environment schema lives in astro.config.mjs (env.schema via astro:env, ADR-050) and defines the PUBLIC_CONTACT_* and PUBLIC_SOCIAL_* variables; SITE_URL / PUBLIC_SITE_URL is read at config-load time.

---
title: ADR Template
lastUpdated: 2025-06-10T00:00:00.000Z
description: >-
Canonical template for Architectural Decision Records (ADRs) in this project.
All new ADRs must follow this structure.
tableOfContents: true
pagefind: false
---
<!--
USAGE: Copy this template to create a new ADR.
File naming: NNN-kebab-case-title.md (e.g., 009-caching-strategy.md)
Frontmatter requirements:
title: 'ADR-NNN: Title Here' (hyphenated ADR-NNN prefix)
description: >- (multi-line YAML string, no markdown)
Plain text summary of the decision
lastUpdated: YYYY-MM-DDT00:00:00.000Z (ISO date, not boolean)
tableOfContents: true
pagefind: true
Body rules:
- First heading must be h2 (## Status), never h1
- No emoji in section headers
- Use plain text for Status values (Proposed, Accepted, Superseded by ADR-NNN, Withdrawn).
A parenthetical qualifier after the canonical word is allowed, e.g.
"Proposed (aspirational)", "Proposed (deferred — revisit trigger)",
"Accepted (amended YYYY-MM-DD: summary)". Only the canonical word carries
binding force (see docs/adr/README.md).
- Use pnpm (never npm) in all script references
Architecture diagrams (optional):
Use Mermaid for flowcharts, sequence diagrams, etc.
See: https://mermaid.js.org/syntax/ for syntax reference.
-->
## Status
[Proposed | Accepted | Superseded by ADR-NNN | Withdrawn]
## Context
What is the issue that we're seeing that is motivating this decision or change? Provide enough context so that someone reading this in the future understands the "why" behind the decision.
## Decision Drivers
- **Driver 1**: [e.g., Performance requirements]
- **Driver 2**: [e.g., Developer experience]
- **Driver 3**: [e.g., Maintenance burden]
- **Driver 4**: [e.g., Cost considerations]
## Considered Options
### Option 1: [Name]
**Description**: Brief description of this approach
**Pros**:
- Advantage 1
- Advantage 2
**Cons**:
- Disadvantage 1
- Disadvantage 2
### Option 2: [Name]
**Description**: Brief description of this approach
**Pros**:
- Advantage 1
- Advantage 2
**Cons**:
- Disadvantage 1
- Disadvantage 2
### Option 3: [Name]
**Description**: Brief description of this approach
**Pros**:
- Advantage 1
- Advantage 2
**Cons**:
- Disadvantage 1
- Disadvantage 2
## Decision
We will go with **Option X** because [justification].
### Implementation Details
```typescript
// Include code examples if relevant
const example = 'implementation details';
```
## Consequences
### Positive
- What becomes easier or better as a result of this change
- Performance improvements expected
- Developer experience improvements
### Negative
- What becomes more difficult
- Technical debt we're accepting
- Additional complexity introduced
### Neutral
- Things that change but aren't necessarily better or worse
- Migration requirements
- Training needs
## Validation
How will we know if this decision was correct?
- **Metric 1**: [e.g., Bundle size remains under 160KB]
- **Metric 2**: [e.g., Build time under 2 minutes]
- **Metric 3**: [e.g., Developer satisfaction survey]
## References
- [Link to relevant documentation]
- [Link to proof of concept]
- [Link to benchmark results]
- [Link to team discussion]
## Notes
Additional implementation notes, migration strategies, or other relevant information that doesn't fit in the sections above.
---
**Date**: YYYY-MM-DD\
**Participants**: [List of people involved in the decision]\
**Outcome**: [Accepted | Rejected | Deferred]
docs/git-workflow.md
## Branch Strategy
### Main Branches
- `master` - Production-ready code (the starter's default branch; use `main` in your own fork if you prefer)
- `develop` - Integration branch (Showcase only)
### Feature Branches
- `feature/*` - New features
- `fix/*` - Bug fixes
- `docs/*` - Documentation updates
### Workflow
1. Create feature branch from the default branch
2. Make changes with conventional commits
3. Open PR with description
4. Merge after review (Showcase) or self-merge (MVP)
### Commit Convention
- `feat:` - New feature
- `fix:` - Bug fix
- `docs:` - Documentation
- `style:` - Formatting
- `refactor:` - Code restructuring
- `perf:` - Performance improvement
- `test:` - Testing
- `chore:` - Maintenance
  1. Wrong Package Manager: Mixing npm/yarn/pnpm causes lockfile conflicts

    • Solution: Commit .npmrc with engine-strict=true
  2. Loose TypeScript: Starting without strict mode makes it hard to enable later

    • Solution: Always start with strict mode, add // @ts-expect-error sparingly
  3. Missing Git Hooks: Code quality degrades without automation

    • Solution: Set up hooks before writing code
  4. Environment Variable Confusion: Hardcoded values in code

    • Solution: Use .env.example and validate on startup
  • Repository initialized with correct .gitignore
  • Package manager locked (pnpm-lock.yaml committed)
  • Node.js version specified (.nvmrc file)
  • TypeScript in strict mode
  • Git hooks functioning (test with commit)
  • Branch strategy documented
  • Environment variables structured
  • ADR template and first decision recorded
  • README has basic project information

If critical issues found after Phase 0:

  1. Package Manager Issues:

    Terminal window
    rm -rf node_modules pnpm-lock.yaml
    pnpm install
  2. Git Configuration Issues:

    Terminal window
    rm -rf .git
    git init
    # Re-apply configuration
  3. Framework Version Issues:

    • Update package.json to stable version
    • Clear cache: pnpm store prune
    • Reinstall dependencies
  • package.json - Verify scripts and dependencies
  • tsconfig.json - TypeScript configuration
  • .husky/pre-commit - Git hooks
  • docs/adr/000-starter-decisions.md - Key decisions
  • “Set up an Astro project on the current stable major with TypeScript strict mode”
  • “Configure Biome for Astro project”
  • “Create Git hooks for code quality”
  • “Write ADR for foundation decisions”
  • Project type (portfolio, blog, marketing)
  • Team size (solo vs team)
  • Deployment target (Cloudflare Pages, Vercel, etc.)