Use hybrid mode of rendering

pull/5494/head
Kamran Ahmed 8 months ago
parent 1d04dd9970
commit 9d63306fc9
  1. 11
      astro.config.mjs
  2. 40
      src/pages/[roadmapId]/[...topicId].astro
  3. 26
      src/pages/[roadmapId]/index.astro
  4. 75
      src/pages/[roadmapId]/index.json.ts
  5. 17
      src/pages/authors/[authorId].astro
  6. 31
      src/pages/authors/[authorId].json.ts
  7. 26
      src/pages/best-practices/[bestPracticeId]/[...topicId].astro
  8. 30
      src/pages/best-practices/[bestPracticeId]/index.astro
  9. 52
      src/pages/best-practices/[bestPracticeId]/index.json.ts
  10. 27
      src/pages/g/[linkGroupId]/[linkId].astro
  11. 26
      src/pages/guides/[guideId].astro
  12. 24
      src/pages/questions/[questionGroupId].astro
  13. 2
      src/pages/r/[customRoadmapSlug].astro
  14. 12
      src/pages/u/[username].astro
  15. 2
      src/pages/v1-stats.json.ts
  16. 19
      src/pages/videos/[videoId].astro

@ -41,18 +41,11 @@ export default defineConfig({
], ],
], ],
}, },
// @FIXME: output: 'hybrid',
// This should be "hybrid" but there is a bug in the current version of Astro
// that adds trailing slashes to the URLs when using "hybrid" mode.
// ----------------------------------------------
// https://github.com/withastro/astro/issues/7808
// ----------------------------------------------
// For now, we are using "server" mode and then using cloudfront to cache the
// pages and serve them as static.
output: 'server',
adapter: node({ adapter: node({
mode: 'standalone', mode: 'standalone',
}), }),
trailingSlash: 'never',
integrations: [ integrations: [
tailwind({ tailwind({
config: { config: {

@ -1,22 +1,32 @@
--- ---
import { getRoadmapTopicFiles } from '../../lib/roadmap-topic'; import {
getRoadmapTopicFiles,
export const partial = true; type RoadmapTopicFileType,
} from '../../lib/roadmap-topic';
const { topicId, roadmapId } = Astro.params;
if (!topicId) { export async function getStaticPaths() {
return new Response(); 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,
};
});
} }
const topicSlug = `/${roadmapId}/${topicId}`; export const partial = true;
const topicPathMapping = await getRoadmapTopicFiles();
const topicDetails = topicPathMapping[topicSlug];
if (!topicDetails) {
return Astro.redirect('/404');
}
const { file } = topicDetails; const { topicId } = Astro.params;
const { file, url, roadmapId, roadmap, heading } =
Astro.props as RoadmapTopicFileType;
const fileWithoutBasePath = file.file?.replace(/.+?\/src\/data/, '/src/data'); const fileWithoutBasePath = file.file?.replace(/.+?\/src\/data/, '/src/data');
const gitHubUrl = `https://github.com/kamranahmedse/developer-roadmap/tree/master${fileWithoutBasePath}`; const gitHubUrl = `https://github.com/kamranahmedse/developer-roadmap/tree/master${fileWithoutBasePath}`;

@ -13,25 +13,27 @@ import {
generateFAQSchema, generateFAQSchema,
} from '../../lib/jsonld-schema'; } from '../../lib/jsonld-schema';
import { getOpenGraphImageUrl } from '../../lib/open-graph'; import { getOpenGraphImageUrl } from '../../lib/open-graph';
import { type RoadmapFrontmatter, getRoadmapIds } from '../../lib/roadmap';
import { export async function getStaticPaths() {
getRoadmapById, const roadmapIds = await getRoadmapIds();
type RoadmapFrontmatter,
getRoadmapFaqsById, return roadmapIds.map((roadmapId) => ({
} from '../../lib/roadmap'; params: { roadmapId },
}));
}
interface Params extends Record<string, string | undefined> { interface Params extends Record<string, string | undefined> {
roadmapId: string; roadmapId: string;
} }
const { roadmapId } = Astro.params as Params; const { roadmapId } = Astro.params as Params;
const roadmapFile = await import(
const roadmapFile = await getRoadmapById(roadmapId).catch(() => null); `../../data/roadmaps/${roadmapId}/${roadmapId}.md`
if (!roadmapFile) { );
return Astro.redirect('/404'); const { faqs: roadmapFAQs = [] } = await import(
} `../../data/roadmaps/${roadmapId}/faqs.astro`
);
const roadmapFAQs = await getRoadmapFaqsById(roadmapId);
const roadmapData = roadmapFile.frontmatter as RoadmapFrontmatter; const roadmapData = roadmapFile.frontmatter as RoadmapFrontmatter;
let jsonLdSchema = []; let jsonLdSchema = [];

@ -1,59 +1,30 @@
import type { APIRoute } from 'astro'; import type { APIRoute } from 'astro';
export const GET: APIRoute = async function ({ params, url, request, props }) { export async function getStaticPaths() {
const { roadmapId: fullRoadmapId } = params; const roadmapJsons = import.meta.glob('/src/data/roadmaps/**/*.json', {
if (!fullRoadmapId) { eager: true,
return new Response( });
JSON.stringify({
data: null,
error: {
message: 'Roadmap not found',
},
}),
{
status: 500,
headers: {
'Content-Type': 'application/json',
},
},
);
}
// to account for `roadmap/roadmap-beginner.json` files
const roadmapId =
fullRoadmapId?.indexOf('-beginner') !== -1
? fullRoadmapId.replace('-beginner', '')
: fullRoadmapId;
const fileName = return Object.keys(roadmapJsons).map((filePath) => {
roadmapId === fullRoadmapId ? `${roadmapId}.json` : `${fullRoadmapId}.json`; const roadmapId = filePath.split('/').pop()?.replace('.json', '');
const roadmapJson = roadmapJsons[filePath] as Record<string, any>;
try { return {
const roadmapJson = await import( params: {
/* @vite-ignore */ `../../data/roadmaps/${roadmapId}/${fileName}` roadmapId,
);
return new Response(JSON.stringify(roadmapJson), {
status: 200,
headers: {
'Content-Type': 'application/json',
}, },
}); props: {
} catch (error) { roadmapJson: roadmapJson?.default,
return new Response(
JSON.stringify({
data: null,
error: {
message: 'Roadmap not found',
detail: (error as any).message,
},
}),
{
status: 500,
headers: {
'Content-Type': 'application/json',
},
}, },
); };
} });
}
export const GET: APIRoute = async function ({ params, request, props }) {
return new Response(JSON.stringify(props.roadmapJson), {
status: 200,
headers: {
'Content-Type': 'application/json',
},
});
}; };

@ -4,21 +4,22 @@ import AstroIcon from '../../components/AstroIcon.astro';
import { getGuidesByAuthor } from '../../lib/guide'; import { getGuidesByAuthor } from '../../lib/guide';
import { getVideosByAuthor } from '../../lib/video'; import { getVideosByAuthor } from '../../lib/video';
import GuideListItem from '../../components/GuideListItem.astro'; import GuideListItem from '../../components/GuideListItem.astro';
import { getAuthorById } from '../../lib/author'; import { getAuthorById, getAuthorIds } from '../../lib/author';
import VideoListItem from '../../components/VideoListItem.astro'; import VideoListItem from '../../components/VideoListItem.astro';
interface Params extends Record<string, string | undefined> {} interface Params extends Record<string, string | undefined> {}
const { authorId } = Astro.params; export async function getStaticPaths() {
if (!authorId) { const authorIds = await getAuthorIds();
return Astro.redirect('/404');
}
const author = await getAuthorById(authorId); return authorIds.map((authorId) => ({
if (!author) { params: { authorId },
return Astro.redirect('/404'); }));
} }
const { authorId } = Astro.params;
const author = await getAuthorById(authorId);
const authorFrontmatter = author.frontmatter; const authorFrontmatter = author.frontmatter;
const guides = await getGuidesByAuthor(authorId); const guides = await getGuidesByAuthor(authorId);

@ -1,20 +1,25 @@
import type { APIRoute } from 'astro'; import type { APIRoute } from 'astro';
import { getAuthorById } from '../../lib/author'; import { getAuthorById, getAuthorIds } from '../../lib/author';
export const GET: APIRoute = async function ({ params, request, props }) { export async function getStaticPaths() {
const { authorId } = params as { authorId: string }; const authorIds = await getAuthorIds();
return await Promise.all(
authorIds.map(async (authorId) => {
const authorDetails = await getAuthorById(authorId);
const authorDetails = await getAuthorById(authorId); return {
if (!authorDetails) { params: { authorId },
return new Response(JSON.stringify({ error: 'Not found' }), { props: {
status: 404, authorDetails: authorDetails?.frontmatter || {},
headers: { },
'Content-Type': 'application/json', };
}, }),
}); );
} }
return new Response(JSON.stringify(authorDetails?.frontmatter), { export const GET: APIRoute = async function ({ params, request, props }) {
return new Response(JSON.stringify(props.authorDetails), {
status: 200, status: 200,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',

@ -1,16 +1,26 @@
--- ---
import { getAllBestPracticeTopicFiles } from '../../../lib/best-practice-topic'; import { getAllBestPracticeTopicFiles } from '../../../lib/best-practice-topic';
import type { BestPracticeTopicFileType } from '../../../lib/best-practice-topic';
const { topicId, bestPracticeId } = Astro.params; export async function getStaticPaths() {
if (!topicId) { const topicPathMapping = await getAllBestPracticeTopicFiles();
return new Response();
} return Object.keys(topicPathMapping).map((topicSlug) => {
const topicDetails = topicPathMapping[topicSlug];
const bestPracticeId = topicDetails.bestPracticeId;
const topicId = topicSlug.replace(`/${bestPracticeId}/`, '');
const topicSlug = `/${bestPracticeId}/${topicId}`; return {
const topicPathMapping = await getAllBestPracticeTopicFiles(); params: {
topicId,
bestPracticeId,
},
props: topicDetails,
};
});
}
const topicDetails = topicPathMapping[topicSlug]; const { file } = Astro.props as BestPracticeTopicFileType;
const { file } = topicDetails;
const fileWithoutBasePath = file.file?.replace(/.+?\/src\/data/, '/src/data'); const fileWithoutBasePath = file.file?.replace(/.+?\/src\/data/, '/src/data');
const gitHubUrl = `https://github.com/kamranahmedse/developer-roadmap/tree/master${fileWithoutBasePath}`; const gitHubUrl = `https://github.com/kamranahmedse/developer-roadmap/tree/master${fileWithoutBasePath}`;

@ -7,13 +7,24 @@ import { TopicDetail } from '../../../components/TopicDetail/TopicDetail';
import UpcomingForm from '../../../components/UpcomingForm.astro'; import UpcomingForm from '../../../components/UpcomingForm.astro';
import BaseLayout from '../../../layouts/BaseLayout.astro'; import BaseLayout from '../../../layouts/BaseLayout.astro';
import { UserProgressModal } from '../../../components/UserProgress/UserProgressModal'; import { UserProgressModal } from '../../../components/UserProgress/UserProgressModal';
import {
type BestPracticeFileType,
type BestPracticeFrontmatter,
getBestPracticeById,
} from '../../../lib/best-practice';
import { generateArticleSchema } from '../../../lib/jsonld-schema'; import { generateArticleSchema } from '../../../lib/jsonld-schema';
import { getOpenGraphImageUrl } from '../../../lib/open-graph'; import { getOpenGraphImageUrl } from '../../../lib/open-graph';
import {
BestPracticeFileType,
BestPracticeFrontmatter,
getAllBestPractices,
} from '../../../lib/best-practice';
export async function getStaticPaths() {
const bestPractices = await getAllBestPractices();
return bestPractices.map((bestPractice: BestPracticeFileType) => ({
params: { bestPracticeId: bestPractice.id },
props: {
bestPractice: bestPractice,
},
}));
}
interface Params extends Record<string, string | undefined> { interface Params extends Record<string, string | undefined> {
bestPracticeId: string; bestPracticeId: string;
@ -24,14 +35,7 @@ interface Props {
} }
const { bestPracticeId } = Astro.params as Params; const { bestPracticeId } = Astro.params as Params;
const bestPractice = await getBestPracticeById(bestPracticeId).catch( const { bestPractice } = Astro.props as Props;
() => null,
);
if (!bestPractice) {
return Astro.redirect('/404');
}
const bestPracticeData = bestPractice.frontmatter as BestPracticeFrontmatter; const bestPracticeData = bestPractice.frontmatter as BestPracticeFrontmatter;
let jsonLdSchema = []; let jsonLdSchema = [];

@ -1,33 +1,33 @@
import type { APIRoute } from 'astro'; import type { APIRoute } from 'astro';
export const GET: APIRoute = async function ({ params, request, props }) { export async function getStaticPaths() {
const { bestPracticeId } = params; const bestPracticeJsons = await import.meta.glob(
'/src/data/best-practices/**/*.json',
{
eager: true,
},
);
try { return Object.keys(bestPracticeJsons).map((filePath) => {
const roadmapJson = await import( const bestPracticeId = filePath.split('/').pop()?.replace('.json', '');
`../../../data/best-practices/${bestPracticeId}/${bestPracticeId}.json` const bestPracticeJson = bestPracticeJsons[filePath] as Record<string, any>;
);
return new Response(JSON.stringify(roadmapJson), { return {
status: 200, params: {
headers: { bestPracticeId,
'Content-Type': 'application/json',
}, },
}); props: {
} catch (error) { bestPracticeJson: bestPracticeJson?.default,
return new Response(
JSON.stringify({
data: null,
error: {
message: 'Best Practices not found',
},
}),
{
status: 500,
headers: {
'Content-Type': 'application/json',
},
}, },
); };
} });
}
export const GET: APIRoute = async function ({ params, request, props }) {
return new Response(JSON.stringify(props.bestPracticeJson), {
status: 200,
headers: {
'content-type': 'application/json',
},
});
}; };

@ -1,14 +1,31 @@
--- ---
import BaseLayout from '../../../layouts/BaseLayout.astro'; import BaseLayout from '../../../layouts/BaseLayout.astro';
import SkeletonLayout from '../../../layouts/SkeletonLayout.astro'; import SkeletonLayout from '../../../layouts/SkeletonLayout.astro';
import { getLinkGroupById } from '../../../lib/link-group'; import { getAllLinkGroups } from '../../../lib/link-group';
const { linkId } = Astro.params; export async function getStaticPaths() {
const linkGroup = await getLinkGroupById(linkId!).catch(() => null); const linkGroups = await getAllLinkGroups();
if (!linkGroup) {
return Astro.redirect('/404'); return linkGroups.flatMap((linkGroup) => {
const linkGroupLinks = linkGroup.frontmatter;
return Object.keys(linkGroupLinks).map((slug) => {
return {
params: {
linkGroupId: linkGroup.id,
linkId: slug,
},
props: {
linkGroup,
},
};
});
});
} }
const { linkId } = Astro.params;
const { linkGroup } = Astro.props;
const fullUrl = linkGroup.frontmatter[linkId!]; const fullUrl = linkGroup.frontmatter[linkId!];
--- ---

@ -2,23 +2,27 @@
import GuideContent from '../../components/Guide/GuideContent.astro'; import GuideContent from '../../components/Guide/GuideContent.astro';
import GuideHeader from '../../components/GuideHeader.astro'; import GuideHeader from '../../components/GuideHeader.astro';
import BaseLayout from '../../layouts/BaseLayout.astro'; import BaseLayout from '../../layouts/BaseLayout.astro';
import { getGuideById } from '../../lib/guide'; import { getAllGuides, type GuideFileType } from '../../lib/guide';
import { getOpenGraphImageUrl } from '../../lib/open-graph'; import { getOpenGraphImageUrl } from '../../lib/open-graph';
interface Params extends Record<string, string | undefined> { export interface Props {
guideId: string; guide: GuideFileType;
} }
const { guideId } = Astro.params; export async function getStaticPaths() {
if (!guideId) { const guides = (await getAllGuides()).filter(
return Astro.redirect('/404'); (guide) => !guide.frontmatter.excludedBySlug,
} );
const guide = await getGuideById(guideId!).catch(() => null);
if (!guide) { return guides.map((guide) => ({
return Astro.redirect('/404'); params: { guideId: guide.id },
props: { guide },
}));
} }
const { frontmatter: guideData } = guide; const { guideId } = Astro.params;
const { guide } = Astro.props;
const { frontmatter: guideData, author } = guide;
const ogImageUrl = getOpenGraphImageUrl({ const ogImageUrl = getOpenGraphImageUrl({
group: 'guides', group: 'guides',

@ -6,16 +6,26 @@ import Footer from '../../components/Footer.astro';
import AstroIcon from '../../components/AstroIcon.astro'; import AstroIcon from '../../components/AstroIcon.astro';
import { QuestionsList } from '../../components/Questions/QuestionsList'; import { QuestionsList } from '../../components/Questions/QuestionsList';
import { getQuestionGroupById } from '../../lib/question-group'; import {
getAllQuestionGroups,
type QuestionGroupType,
} from '../../lib/question-group';
const { questionGroupId } = Astro.params; export interface Props {
const questionGroup = await getQuestionGroupById(questionGroupId!).catch( questionGroup: QuestionGroupType;
() => null,
);
if (!questionGroup) {
return Astro.redirect('/404');
} }
export async function getStaticPaths() {
const questionGroups = await getAllQuestionGroups();
return questionGroups.map((questionGroup) => {
return {
params: { questionGroupId: questionGroup.id },
props: { questionGroup },
};
});
}
const { questionGroup } = Astro.props;
const { frontmatter } = questionGroup; const { frontmatter } = questionGroup;
--- ---

@ -5,6 +5,8 @@ import { SkeletonRoadmapHeader } from '../../components/CustomRoadmap/SkeletonRo
import Loader from '../../components/Loader.astro'; import Loader from '../../components/Loader.astro';
import ProgressHelpPopup from '../../components/ProgressHelpPopup.astro'; import ProgressHelpPopup from '../../components/ProgressHelpPopup.astro';
export const prerender = false;
const { customRoadmapSlug } = Astro.params; const { customRoadmapSlug } = Astro.params;
--- ---

@ -1,10 +1,12 @@
--- ---
import { FrownIcon } from 'lucide-react'; import { FrownIcon } from 'lucide-react';
import { userApi } from '../../../api/user'; import { userApi } from '../../api/user';
import AccountLayout from '../../../layouts/AccountLayout.astro'; import AccountLayout from '../../layouts/AccountLayout.astro';
import { UserPublicProfilePage } from '../../../components/UserPublicProfile/UserPublicProfilePage'; import { UserPublicProfilePage } from '../../components/UserPublicProfile/UserPublicProfilePage';
import OpenSourceBanner from '../../../components/OpenSourceBanner.astro'; import OpenSourceBanner from '../../components/OpenSourceBanner.astro';
import Footer from '../../../components/Footer.astro'; import Footer from '../../components/Footer.astro';
export const prerender = false;
interface Params extends Record<string, string | undefined> { interface Params extends Record<string, string | undefined> {
username: string; username: string;

@ -1,7 +1,5 @@
import { execSync } from 'child_process'; import { execSync } from 'child_process';
export const prerender = true;
export async function GET() { export async function GET() {
const commitHash = execSync('git rev-parse HEAD').toString().trim(); const commitHash = execSync('git rev-parse HEAD').toString().trim();
const commitDate = execSync('git log -1 --format=%cd').toString().trim(); const commitDate = execSync('git log -1 --format=%cd').toString().trim();

@ -1,14 +1,23 @@
--- ---
import VideoHeader from '../../components/VideoHeader.astro'; import VideoHeader from '../../components/VideoHeader.astro';
import BaseLayout from '../../layouts/BaseLayout.astro'; import BaseLayout from '../../layouts/BaseLayout.astro';
import { getVideoById } from '../../lib/video'; import { getAllVideos, VideoFileType } from '../../lib/video';
const { videoId } = Astro.params; export interface Props {
video: VideoFileType;
}
export async function getStaticPaths() {
const videos = await getAllVideos();
const video = await getVideoById(videoId).catch(() => null); return videos.map((video) => ({
if (!video) { params: { videoId: video.id },
return Astro.redirect('/404'); props: { video },
}));
} }
const { videoId } = Astro.params;
const { video } = Astro.props;
--- ---
<BaseLayout <BaseLayout

Loading…
Cancel
Save