DEV Community

Mateen Anjum
Mateen Anjum

Posted on

Building a VFR Flight Weather App with Next.js and Aviation APIs

TL;DR: I built a weather app for pilots that fetches METAR/TAF data from aviationweather.gov and provides VFR flying assessments for Cessna 172 aircraft. The app tells you if conditions are GO, CAUTION, or NO-GO based on real aviation minimums.

The Problem

I rebuilt my old PHP weather app using Next.js 16 and decided to take it further. As a student pilot, I wanted an app that would tell me if the weather was suitable for VFR flight in a Cessna 172. Not just "it's cloudy" but actual aviation weather data: METAR observations, TAF forecasts, and a clear GO/CAUTION/NO-GO assessment.

Existing aviation weather sites show raw data. I wanted something that would interpret that data against Cessna 172 operating limits and give me an immediate answer.

What I Built

The app has two modes:

Mode Data Source Purpose
Weather OpenWeatherMap General weather for any city
Aviation aviationweather.gov METAR/TAF with VFR assessment

Aviation mode is the default. Enter an airport code (CYYZ), city name (Toronto), or airport name and get:

  1. VFR Assessment: GO (green), CAUTION (yellow), or NO-GO (red)
  2. Flight Category: VFR, MVFR, IFR, or LIFR
  3. Decoded METAR: Temperature, wind, visibility, ceiling, altimeter
  4. Raw TAF: Terminal aerodrome forecast for the next 24 hours
  5. Factor Breakdown: What's driving the assessment

Cessna 172 VFR Limits

The assessment logic uses real C172 operating limits:

Factor GO CAUTION NO-GO
Ceiling 3000+ ft 1500-3000 ft <1500 ft
Visibility 5+ SM 3-5 SM <3 SM
Wind <15 kts 15-20 kts >20 kts
Crosswind <12 kts 12-15 kts >15 kts
Gusts <20 kts 20-25 kts >25 kts

Precipitation and thunderstorms also factor in. Any NO-GO factor makes the overall assessment NO-GO.

Technical Implementation

Aviation API Route

The app proxies requests to aviationweather.gov to keep the architecture clean:

// app/api/aviation/route.ts
const AVIATION_WEATHER_BASE = 'https://aviationweather.gov/api/data';

async function fetchMetar(icao: string): Promise<MetarResponse | null> {
  const url = `${AVIATION_WEATHER_BASE}/metar?ids=${icao}&format=json`;
  const response = await fetch(url, {
    next: { revalidate: 300 }, // Cache for 5 minutes
  });
  const data = await response.json();
  return data[0] || null;
}
Enter fullscreen mode Exit fullscreen mode

aviationweather.gov is free, requires no API key, and includes Canadian airports. The JSON format makes parsing straightforward.

VFR Assessment Logic

Each weather factor gets assessed independently:

function assessCeiling(clouds: CloudLayer[]): FactorAssessment {
  const ceiling = getCeilingFromClouds(clouds);

  if (ceiling === null) {
    return { status: 'GO', message: 'Clear or no ceiling' };
  }

  if (ceiling >= 3000) {
    return { status: 'GO', value: ceiling, message: `${ceiling} ft AGL` };
  }

  if (ceiling >= 1500) {
    return { status: 'CAUTION', value: ceiling, message: `${ceiling} ft AGL (marginal)` };
  }

  return { status: 'NO-GO', value: ceiling, message: `${ceiling} ft AGL (too low)` };
}
Enter fullscreen mode Exit fullscreen mode

The overall status is the worst of all factors. One NO-GO means NO-GO.

Flexible Airport Search

The search accepts multiple input formats:

export function normalizeToIcao(input: string): string | null {
  const trimmed = input.trim().toUpperCase();

  // Already an ICAO code?
  if (/^[A-Z]{4}$/.test(trimmed)) {
    return trimmed;
  }

  // Search by city or airport name
  const matches = searchAirports(input);
  return matches.length > 0 ? matches[0].icao : null;
}
Enter fullscreen mode Exit fullscreen mode

Type "Toronto" and it finds CYYZ. Type "Buttonville" and it finds CYKZ.

Dynamic Backgrounds

The background gradient changes based on assessment:

export function getAviationGradient(status: AssessmentStatus): string {
  switch (status) {
    case 'GO':
      return 'from-emerald-500 via-green-600 to-teal-700';
    case 'CAUTION':
      return 'from-amber-400 via-orange-500 to-yellow-600';
    case 'NO-GO':
      return 'from-red-500 via-rose-600 to-red-800';
  }
}
Enter fullscreen mode Exit fullscreen mode

You know the assessment before reading a single word.

Architecture

weather-app/
├── app/
│   ├── api/
│   │   ├── weather/route.ts    # OpenWeatherMap proxy
│   │   └── aviation/route.ts   # aviationweather.gov proxy
│   └── page.tsx                # Main page with mode toggle
├── components/
│   ├── airport-search.tsx      # ICAO/city/name search
│   ├── vfr-assessment.tsx      # GO/CAUTION/NO-GO card
│   ├── metar-display.tsx       # Decoded METAR/TAF
│   ├── aviation-display.tsx    # Aviation mode container
│   └── ...                     # Weather mode components
└── lib/
    ├── aviation-types.ts       # TypeScript types
    └── aviation-utils.ts       # VFR assessment logic
Enter fullscreen mode Exit fullscreen mode

Results

Current conditions as I write this:

Airport Assessment Flight Category Reason
CYYZ (Toronto) CAUTION MVFR 2100 ft ceiling, light snow
CYVR (Vancouver) GO VFR 8500 ft ceiling, clear

The app correctly identifies marginal conditions and provides actionable recommendations.

Tech Stack

Technology Purpose
Next.js 16 App Router, API routes
React 19 UI components
TypeScript 5 Type safety
Tailwind CSS 4 Styling
Framer Motion Animations
aviationweather.gov METAR/TAF data

Try It

Live Demo: weather-app-five-ivory-48.vercel.app

Repository: github.com/mateenali66/Weather-forecast-openweather

The aviation mode loads by default. Enter any Canadian airport code (CYYZ, CYKZ, CYVR) or search by city name.

Lessons Learned

  1. aviationweather.gov is underrated. Free, no auth, JSON format, global coverage including Canada.

  2. VFR limits are well-defined. The Cessna 172 POH and Transport Canada regulations give clear minimums. Translating them to code was straightforward.

  3. Traffic light UX works. GO/CAUTION/NO-GO is immediately understood. No pilot needs to interpret a dashboard.

  4. Type safety matters for aviation. TypeScript caught several unit mismatches (feet vs meters, knots vs km/h) during development.

  5. Canadian airports use ICAO codes. CYYZ not YYZ. The "C" prefix is required.

Top comments (0)