diff --git a/src/pages/[roadmapId]/index.json.ts b/src/pages/[roadmapId].json.ts
similarity index 95%
rename from src/pages/[roadmapId]/index.json.ts
rename to src/pages/[roadmapId].json.ts
index 7d4b6c0f3..56a9cc507 100644
--- a/src/pages/[roadmapId]/index.json.ts
+++ b/src/pages/[roadmapId].json.ts
@@ -1,5 +1,7 @@
import type { APIRoute } from 'astro';
+export const prerender = true;
+
export async function getStaticPaths() {
const roadmapJsons = import.meta.glob('/src/data/roadmaps/**/*.json', {
eager: true,
diff --git a/src/pages/[roadmapId]/[...topicId].astro b/src/pages/[roadmapId]/[...topicId].astro
index aec201c85..8682a1e2d 100644
--- a/src/pages/[roadmapId]/[...topicId].astro
+++ b/src/pages/[roadmapId]/[...topicId].astro
@@ -1,37 +1,87 @@
---
-import {
- getRoadmapTopicFiles,
- type RoadmapTopicFileType,
-} from '../../lib/roadmap-topic';
-
-export async function getStaticPaths() {
- const topicPathMapping = await getRoadmapTopicFiles();
-
- return Object.keys(topicPathMapping).map((topicSlug) => {
- const topicDetails = topicPathMapping[topicSlug];
- const roadmapId = topicDetails.roadmapId;
- const topicId = topicSlug.replace(`/${roadmapId}/`, '');
-
- return {
- params: {
- topicId,
- roadmapId,
- },
- props: topicDetails,
- };
- });
+import fs from 'node:fs';
+import path from 'node:path';
+import matter from 'gray-matter';
+import MarkdownIt from 'markdown-it-async';
+import { fileURLToPath } from 'node:url';
+
+export const prerender = false;
+
+const { topicId, roadmapId } = Astro.params;
+
+if (!topicId || !roadmapId) {
+ Astro.response.status = 404;
+ Astro.response.statusText = 'Not found';
+
+ return Astro.rewrite('/404');
+}
+
+// Handle nested paths by joining the segments
+const topicPath = Array.isArray(topicId) ? topicId.join('/') : topicId;
+
+// Get the project root directory
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+// hack to make it work. TODO: Fix
+const projectRoot = path.resolve(__dirname, '../../..').replace(/dist$/, '');
+
+// Construct the path to the markdown file
+let contentPath = path.join(
+ projectRoot,
+ 'src',
+ 'data',
+ 'roadmaps',
+ roadmapId,
+ 'content',
+ `${topicPath}.md`,
+);
+
+// Check if file exists
+if (!fs.existsSync(contentPath)) {
+ const indexFilePath = path.join(
+ projectRoot,
+ 'src',
+ 'data',
+ 'roadmaps',
+ roadmapId,
+ 'content',
+ `${topicPath}/index.md`,
+ );
+
+ if (!fs.existsSync(indexFilePath)) {
+ Astro.response.status = 404;
+ Astro.response.statusText = 'Not found';
+
+ return Astro.rewrite('/404');
+ }
+
+ contentPath = indexFilePath;
}
-export const partial = true;
+// Read and parse the markdown file
+const fileContent = fs.readFileSync(contentPath, 'utf-8');
+const { data: frontmatter, content } = matter(fileContent);
-const { topicId } = Astro.params;
-const { file, url, roadmapId, roadmap, heading } =
- Astro.props as RoadmapTopicFileType;
+// Get the roadmap metadata
+const roadmapPath = path.join(
+ projectRoot,
+ 'src',
+ 'data',
+ 'roadmaps',
+ roadmapId,
+ `${roadmapId}.md`,
+);
+const roadmapContent = fs.readFileSync(roadmapPath, 'utf-8');
+const { data: roadmapData } = matter(roadmapContent);
-const fileWithoutBasePath = file.file?.replace(/.+?\/src\/data/, '/src/data');
+const fileWithoutBasePath = contentPath.replace(/.+?\/src\/data/, '/src/data');
const gitHubUrl = `https://github.com/kamranahmedse/developer-roadmap/tree/master${fileWithoutBasePath}`;
+
+const md = MarkdownIt();
+const htmlContent = await md.renderAsync(content);
---
-
+
diff --git a/src/pages/[roadmapId]/courses.astro b/src/pages/[roadmapId]/courses.astro
index d5f9778a9..623f1ea49 100644
--- a/src/pages/[roadmapId]/courses.astro
+++ b/src/pages/[roadmapId]/courses.astro
@@ -5,6 +5,9 @@ import { getOpenGraphImageUrl } from '../../lib/open-graph';
import { type RoadmapFrontmatter, getRoadmapIds } from '../../lib/roadmap';
import CourseStep from '../../components/courses/CourseStep.astro';
import Milestone from '../../components/courses/Milestone.astro';
+import { getProjectsByRoadmapId } from '../../lib/project';
+
+export const prerender = true;
export async function getStaticPaths() {
const roadmapIds = await getRoadmapIds();
@@ -33,7 +36,7 @@ const ogImageUrl =
resourceId: roadmapId,
});
-const descriptionNoun = {
+const descriptionNoun: Record
= {
'AI and Data Scientist': 'AI and Data Science',
'Game Developer': 'Game Development',
'Technical Writer': 'Technical Writing',
@@ -41,12 +44,15 @@ const descriptionNoun = {
};
const title = `${roadmapData.briefTitle} Courses`;
-const description = `Courses and project driven path to help you master ${descriptionNoun[roadmapData.briefTitle] || roadmapData.briefTitle}`;
+const description = `Premium courses to help you master ${descriptionNoun[roadmapData.briefTitle] || roadmapData.briefTitle}`;
const seoTitle = `${roadmapData.briefTitle} Courses`;
const nounTitle =
descriptionNoun[roadmapData.briefTitle] || roadmapData.briefTitle;
const seoDescription = `Seeking ${nounTitle.toLowerCase()} courses to enhance your skills? Explore our top free and paid courses to help you become a ${nounTitle} expert!`;
+
+const projects = await getProjectsByRoadmapId(roadmapId);
+const courses = roadmapData.courses || [];
---
-
-
-
-
-
- Frontend development is a vast field with a lot of tools and
- technologies. We have the frontend roadmap
- which is filled with a lot of free and good resources to help you learn. But sometimes it helps to have a minimalistic
- list of courses and project recommendations to help you get started.
-
-
-
- Below are some of the best courses (paid) and projects to help you
- learn frontend development. These are handpicked and are a great way
- to get started.
-
-
-
- Please note that these are paid courses curated from external
- platforms. We earn a small commission if you purchase the course
- using the links below. This helps us maintain the website and keep
- it free for everyone.
+
+
+
+ Official Courses by roadmap.sh
+ team
+
+ More coming soon
+
+
-
- If you are looking for free resources, you can check out the frontend roadmap. Also, we have a list of projects that you can work on to enhance your skills.
-
+
-
-
-
-
-
-
-
-
+ {
+ courses.length === 0 && (
+
+
+ No courses available yet
+
+
+ We're working on creating premium courses for this roadmap.
+ Check back soon!
+
+
+ )
+ }
diff --git a/src/pages/[roadmapId]/courses.json.ts b/src/pages/[roadmapId]/courses.json.ts
deleted file mode 100644
index 20155b72c..000000000
--- a/src/pages/[roadmapId]/courses.json.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import type { APIRoute } from 'astro';
-import { getRoadmapIds } from '../../lib/roadmap.ts';
-
-export async function getStaticPaths() {
- const coursesJsons: Record
= import.meta.glob(
- '/src/data/roadmaps/**/courses.json',
- {
- eager: true,
- },
- );
-
- const roadmapIds = await getRoadmapIds();
-
- return roadmapIds.map((roadmapId) => ({
- params: {
- roadmapId,
- },
- props: {
- courses:
- coursesJsons[`/src/data/roadmaps/${roadmapId}/courses.json`]?.default ||
- {},
- },
- }));
-}
-
-export const GET: APIRoute = async function ({ params, request, props }) {
- return new Response(JSON.stringify(props.courses), {
- status: 200,
- headers: {
- 'Content-Type': 'application/json',
- },
- });
-};
diff --git a/src/pages/[roadmapId]/index.astro b/src/pages/[roadmapId]/index.astro
index 346f9fca7..c57c1fe03 100644
--- a/src/pages/[roadmapId]/index.astro
+++ b/src/pages/[roadmapId]/index.astro
@@ -19,6 +19,8 @@ import { RoadmapTitleQuestion } from '../../components/RoadmapTitleQuestion';
import ResourceProgressStats from '../../components/ResourceProgressStats.astro';
import { getProjectsByRoadmapId } from '../../lib/project';
+export const prerender = true;
+
export async function getStaticPaths() {
const roadmapIds = await getRoadmapIds();
@@ -70,6 +72,7 @@ const ogImageUrl =
const question = roadmapData?.question;
const note = roadmapData.note;
const projects = await getProjectsByRoadmapId(roadmapId);
+const courses = roadmapData.courses || [];
---
@@ -132,7 +136,7 @@ const projects = await getProjectsByRoadmapId(roadmapId);
-
+
diff --git a/src/pages/[roadmapId]/svg.astro b/src/pages/[roadmapId]/svg.astro
index 1d9823898..d57b51c69 100644
--- a/src/pages/[roadmapId]/svg.astro
+++ b/src/pages/[roadmapId]/svg.astro
@@ -5,6 +5,8 @@ import SkeletonLayout from '../../layouts/SkeletonLayout.astro';
import { getOpenGraphImageUrl } from '../../lib/open-graph';
import { type RoadmapFrontmatter, getRoadmapIds } from '../../lib/roadmap';
+export const prerender = true;
+
export async function getStaticPaths() {
const roadmapIds = await getRoadmapIds();
@@ -42,7 +44,7 @@ const ogImageUrl =
resourceType='roadmap'
noIndex={true}
>
-
+
{
roadmapData?.renderer === 'editor' ? (
+
diff --git a/src/pages/ai/explore.astro b/src/pages/ai-roadmaps/explore.astro
similarity index 93%
rename from src/pages/ai/explore.astro
rename to src/pages/ai-roadmaps/explore.astro
index 47123b436..55e827348 100644
--- a/src/pages/ai/explore.astro
+++ b/src/pages/ai-roadmaps/explore.astro
@@ -3,7 +3,7 @@ import { ExploreAIRoadmap } from '../../components/ExploreAIRoadmap/ExploreAIRoa
import BaseLayout from '../../layouts/BaseLayout.astro';
---
-
+
diff --git a/src/pages/ai-roadmaps/index.astro b/src/pages/ai-roadmaps/index.astro
new file mode 100644
index 000000000..18ce37941
--- /dev/null
+++ b/src/pages/ai-roadmaps/index.astro
@@ -0,0 +1,10 @@
+---
+import { GenerateRoadmap } from '../../components/GenerateRoadmap/GenerateRoadmap';
+import BaseLayout from '../../layouts/BaseLayout.astro';
+import { CheckSubscriptionVerification } from '../../components/Billing/CheckSubscriptionVerification';
+---
+
+
+
+
+
diff --git a/src/pages/ai-tutor/index.astro b/src/pages/ai-tutor/index.astro
deleted file mode 100644
index 5f2ab8ebf..000000000
--- a/src/pages/ai-tutor/index.astro
+++ /dev/null
@@ -1,17 +0,0 @@
----
-import { AICourse } from '../../components/GenerateCourse/AICourse';
-import BaseLayout from '../../layouts/BaseLayout.astro';
-import { CheckSubscriptionVerification } from '../../components/Billing/CheckSubscriptionVerification';
-
-const ogImage = 'https://roadmap.sh/og-images/ai-tutor.png';
----
-
-
-
-
-
diff --git a/src/pages/ai-tutor/[courseSlug].astro b/src/pages/ai/[courseSlug].astro
similarity index 89%
rename from src/pages/ai-tutor/[courseSlug].astro
rename to src/pages/ai/[courseSlug].astro
index e3c6ec146..616f9305f 100644
--- a/src/pages/ai-tutor/[courseSlug].astro
+++ b/src/pages/ai/[courseSlug].astro
@@ -17,8 +17,9 @@ const { courseSlug } = Astro.params as Params;
briefTitle='AI Tutor'
description='AI Tutor'
keywords={['ai', 'tutor', 'education', 'learning']}
- canonicalUrl={`/ai-tutor/${courseSlug}`}
+ canonicalUrl={`/ai/${courseSlug}`}
>
+
diff --git a/src/pages/ai/index.astro b/src/pages/ai/index.astro
index c2c7c1e6d..3a67adf52 100644
--- a/src/pages/ai/index.astro
+++ b/src/pages/ai/index.astro
@@ -1,8 +1,18 @@
---
-import { GenerateRoadmap } from '../../components/GenerateRoadmap/GenerateRoadmap';
+import { AICourse } from '../../components/GenerateCourse/AICourse';
import BaseLayout from '../../layouts/BaseLayout.astro';
+import { CheckSubscriptionVerification } from '../../components/Billing/CheckSubscriptionVerification';
+
+const ogImage = 'https://roadmap.sh/og-images/ai-tutor.png';
---
-
-
+
+
+
+
diff --git a/src/pages/ai-tutor/search.astro b/src/pages/ai/search.astro
similarity index 91%
rename from src/pages/ai-tutor/search.astro
rename to src/pages/ai/search.astro
index 91edc218c..284e8f4ce 100644
--- a/src/pages/ai-tutor/search.astro
+++ b/src/pages/ai/search.astro
@@ -9,7 +9,8 @@ import { CheckSubscriptionVerification } from '../../components/Billing/CheckSub
briefTitle='AI Tutor'
description='AI Tutor'
keywords={['ai', 'tutor', 'education', 'learning']}
- canonicalUrl='/ai-tutor/search'
+ canonicalUrl='/ai/search'
+ noIndex={true}
>
diff --git a/src/pages/authors/[authorId].astro b/src/pages/authors/[authorId].astro
index 2bbcd920d..2184e8e44 100644
--- a/src/pages/authors/[authorId].astro
+++ b/src/pages/authors/[authorId].astro
@@ -8,6 +8,8 @@ import { getGuidesByAuthor } from '../../lib/guide';
import { getAllQuestionGroups } from '../../lib/question-group';
import { getVideosByAuthor } from '../../lib/video';
+export const prerender = true;
+
interface Params extends Record {}
export async function getStaticPaths() {
@@ -62,7 +64,7 @@ const videos = await getVideosByAuthor(authorId);
-
+
{authorFrontmatter.name}
@@ -121,7 +123,7 @@ const videos = await getVideosByAuthor(authorId);
-
+
{
[...guides, ...questionGuides]
diff --git a/src/pages/authors/[authorId].json.ts b/src/pages/authors/[authorId].json.ts
index 4193292f7..43bf20dbf 100644
--- a/src/pages/authors/[authorId].json.ts
+++ b/src/pages/authors/[authorId].json.ts
@@ -1,6 +1,8 @@
import type { APIRoute } from 'astro';
import { getAuthorById, getAuthorIds } from '../../lib/author';
+export const prerender = true;
+
export async function getStaticPaths() {
const authorIds = await getAuthorIds();
diff --git a/src/pages/best-practices/[bestPracticeId]/index.json.ts b/src/pages/best-practices/[bestPracticeId].json.ts
similarity index 96%
rename from src/pages/best-practices/[bestPracticeId]/index.json.ts
rename to src/pages/best-practices/[bestPracticeId].json.ts
index 00400ce13..2fbdd7409 100644
--- a/src/pages/best-practices/[bestPracticeId]/index.json.ts
+++ b/src/pages/best-practices/[bestPracticeId].json.ts
@@ -1,5 +1,7 @@
import type { APIRoute } from 'astro';
+export const prerender = true;
+
export async function getStaticPaths() {
const bestPracticeJsons = await import.meta.glob(
'/src/data/best-practices/**/*.json',
diff --git a/src/pages/best-practices/[bestPracticeId]/[...topicId].astro b/src/pages/best-practices/[bestPracticeId]/[...topicId].astro
index 1607036b0..ae189b29b 100644
--- a/src/pages/best-practices/[bestPracticeId]/[...topicId].astro
+++ b/src/pages/best-practices/[bestPracticeId]/[...topicId].astro
@@ -2,6 +2,8 @@
import { getAllBestPracticeTopicFiles } from '../../../lib/best-practice-topic';
import type { BestPracticeTopicFileType } from '../../../lib/best-practice-topic';
+export const prerender = true;
+
export async function getStaticPaths() {
const topicPathMapping = await getAllBestPracticeTopicFiles();
diff --git a/src/pages/best-practices/[bestPracticeId]/index.astro b/src/pages/best-practices/[bestPracticeId]/index.astro
index 7fc4173b1..d577a913c 100644
--- a/src/pages/best-practices/[bestPracticeId]/index.astro
+++ b/src/pages/best-practices/[bestPracticeId]/index.astro
@@ -15,6 +15,8 @@ import {
getAllBestPractices,
} from '../../../lib/best-practice';
+export const prerender = true;
+
export async function getStaticPaths() {
const bestPractices = await getAllBestPractices();
@@ -92,7 +94,7 @@ const ogImageUrl = getOpenGraphImageUrl({
{
!bestPracticeData.isUpcoming && bestPracticeData.jsonUrl && (
-
+
diff --git a/src/pages/g/[linkGroupId]/[linkId].astro b/src/pages/g/[linkGroupId]/[linkId].astro
index a63e52f4a..ea6c42499 100644
--- a/src/pages/g/[linkGroupId]/[linkId].astro
+++ b/src/pages/g/[linkGroupId]/[linkId].astro
@@ -3,6 +3,8 @@ import BaseLayout from '../../../layouts/BaseLayout.astro';
import SkeletonLayout from '../../../layouts/SkeletonLayout.astro';
import { getAllLinkGroups } from '../../../lib/link-group';
+export const prerender = true;
+
export async function getStaticPaths() {
const linkGroups = await getAllLinkGroups();
diff --git a/src/pages/get-started.astro b/src/pages/get-started.astro
index b1b70c41a..d696c65fe 100644
--- a/src/pages/get-started.astro
+++ b/src/pages/get-started.astro
@@ -48,7 +48,7 @@ import ChangelogBanner from '../components/ChangelogBanner.astro';
description={'Step by step guides and paths to learn different tools or technologies'}
permalink={'/get-started'}
>
-