Prerequisites
- A basic understanding of Next.js and TypeScript
- An active Strava account
- Access to the Strava API (client ID, client secret, and refresh token)
- A GitHub repository for your Next.js project
Step 1: Setting Up Strava API Service
To simplify Strava API integration, we'll create a new service:
import type { Activity } from "@/types"; | |
import axios from "axios"; | |
export const client = axios.create({ | |
baseURL: "https://www.strava.com/api/v3", | |
}); | |
interface StravaAuthResponse { | |
token_type: string; | |
expires_at: number; | |
expires_in: number; | |
refresh_token: string; | |
access_token: string; | |
} | |
export async function getAccessToken(): Promise<string> { | |
const STRAVA_CLIENT_ID = process.env.STRAVA_CLIENT_ID!; | |
const STRAVA_CLIENT_SECRET = process.env.STRAVA_CLIENT_SECRET!; | |
const STRAVA_REFRESH_TOKEN = process.env.STRAVA_REFRESH_TOKEN!; | |
if (!STRAVA_CLIENT_ID || !STRAVA_CLIENT_SECRET || !STRAVA_REFRESH_TOKEN) { | |
throw new Error( | |
"Missing required environment variables: STRAVA_CLIENT_ID, STRAVA_CLIENT_SECRET, or STRAVA_REFRESH_TOKEN." | |
); | |
} | |
try { | |
const response = await client.post<StravaAuthResponse>("/oauth/token", { | |
client_id: STRAVA_CLIENT_ID, | |
client_secret: STRAVA_CLIENT_SECRET, | |
refresh_token: STRAVA_REFRESH_TOKEN, | |
grant_type: "refresh_token", | |
}); | |
const { access_token, refresh_token, expires_at } = response.data; | |
console.log("New access token fetched successfully."); | |
// Optionally, log the expiration time for debugging (convert from UNIX timestamp) | |
console.log( | |
"Access token expires at:", | |
new Date(expires_at * 1000).toISOString() | |
); | |
// Optionally update refresh token if it changes | |
if (refresh_token !== STRAVA_REFRESH_TOKEN) { | |
console.log("Refresh token has been updated:", refresh_token); | |
// Save this to your secrets storage if necessary | |
} | |
return access_token; | |
} catch (error) { | |
console.error(error); | |
throw new Error("Failed to fetch Strava access token"); | |
} | |
} | |
export async function fetchActivities() { | |
try { | |
const accessToken = await getAccessToken(); | |
return client.get<Activity[]>("/athlete/activities", { | |
headers: { | |
Authorization: `Bearer ${accessToken}`, | |
}, | |
params: { | |
per_page: 3, | |
}, | |
}); | |
} catch (error) { | |
console.error(error); | |
throw new Error("Failed to fetch activities"); | |
} | |
} | |
export async function fetchExtendedActivities() { | |
try { | |
const { data } = await fetchActivities(); | |
const list = await Promise.all(data.map((i) => fetchActivity(i.id))); | |
return { data: list.map((i) => i.data) }; | |
} catch (error) { | |
console.error(error); | |
throw new Error("Failed to fetch activities"); | |
} | |
} | |
export async function fetchActivity(id: number) { | |
try { | |
const accessToken = await getAccessToken(); | |
return client.get<Activity[]>(`/activities/${id}`, { | |
headers: { | |
Authorization: `Bearer ${accessToken}`, | |
}, | |
}); | |
} catch (error) { | |
console.error(error); | |
throw new Error("Failed to fetch activity"); | |
} | |
} |
Step 2: Fetching and Displaying Activities in a Component
To display activities on the frontend, you’ll need to fetch the necessary data and pass it to your React component. In this step, we fetch the data using getStaticProps
, which allows the page content and activities to be pre-rendered at build time.
Eg. in pages/index.tsx
, the data is fetched by calling fetchExtendedActivities
, which retrieves activities data from Strava API. The fetched data is then returned as props, which are passed to the component.
Here’s how you can implement this:
import type { Activity } from "@/types"; | |
import { fetchExtendedActivities } from "@/service/stravaApi"; | |
interface PageProps { | |
activities: Activity[]; | |
} | |
export default function Index({ activities }: PageProps) { | |
return <pre>{JSON.stringify(activities, null, 2)}</pre> | |
} | |
export async function getStaticProps() { | |
const { data: activities } = await fetchExtendedActivities(); | |
return { | |
props: { | |
activities, | |
}, | |
}; | |
} |
Step 4: Configuring GitHub Cron Job
To automate tasks like data fetching, deployments, or updates on a regular basis, you can use a GitHub cron job by configuring a scheduled workflow. This will allow you to trigger your workflow at specific times, such as once a day, at a specific hour, or on particular days of the week.
# Trigger the workflow once a day (adjust the schedule as needed)
schedule:
- cron: "0 0 * * *" # run the workflow at midnight UTC every day
After the job runs, you can monitor the results and logs from the Actions tab in your GitHub repository. If the cron job fails or produces unexpected results, the logs will help you troubleshoot and adjust the workflow accordingly.
Adding an API Route (Optional)
Since we're using Next.js with SSG mode to generate distribution artefacts for GitHub Pages, we'll call the fetchExtendedActivities
method inside getStaticProps
. However, if you want to serve data (activities) from Strava API dynamically to your frontend, consider adding an API route in your Next.js app:
import { fetchExtendedActivities } from "@/service/stravaApi"; | |
import type { NextApiRequest, NextApiResponse } from "next"; | |
type ResponseData = unknown[]; | |
export default async function handler( | |
req: NextApiRequest, | |
res: NextApiResponse<ResponseData> | |
) { | |
const response = await fetchExtendedActivities(); | |
res.status(200).json(response.data); | |
} |
Conclusion
You’ve now set up a Next.js app that pulls in Strava activities, built a backend service, and set up a GitHub cron job to keep things updated regularly. This setup is super flexible, so you can tweak and expand it however you need as your project grows.
Refs.
Image Credits
Photo by Luke Chesser on Unsplash
Top comments (2)
Nice job
Thanks mate! 👏