Compare commits

...

16 Commits

Author SHA1 Message Date
Arik Chakma
7e229ff488 fix: account activity title 2024-04-10 04:04:24 +06:00
Kamran Ahmed
c768cac62f Add cloudfront cache clearing 2024-04-09 13:26:07 +01:00
Kamran Ahmed
3ad7765658 Update rsync options 2024-04-08 05:43:36 +01:00
Kamran Ahmed
0e0a70fcbd Exclude .git 2024-04-08 05:31:30 +01:00
Kamran Ahmed
5c330094f9 Fix broken build 2024-04-08 04:02:26 +01:00
Kamran Ahmed
e9f5462180 Fix broken build 2024-04-08 03:48:28 +01:00
Kamran Ahmed
c9534b30f5 Fix broken build 2024-04-08 03:43:43 +01:00
Kamran Ahmed
8537698d91 Update dependencies in github actions 2024-04-08 03:31:14 +01:00
Kamran Ahmed
9414089714 Add rsync script for deployment 2024-04-08 03:26:14 +01:00
Kamran Ahmed
25965d91a0 Replace hiring with AI button 2024-04-08 02:22:19 +01:00
Ethan Cui
7ce02300de fix: typo Chatty I/O (#5471)
The content's title should be "Chatty I/O" instead of "Chat I/O"
2024-04-07 13:06:02 +06:00
Kamran Ahmed
f0859628f7 Reduce table of content gap 2024-04-04 17:02:37 +01:00
Kamran Ahmed
ff94ea7116 Sticky sidebar 2024-04-04 17:01:39 +01:00
Arik Chakma
abad548961 feat: add footer for ai generated roadmaps (#5461)
* feat: add footer for ai generated roadmaps

* fix: add layout in explore page
2024-04-03 20:57:50 +01:00
Arik Chakma
6e81855645 fix: text wrap in AI search roadmap (#5462) 2024-04-03 19:20:38 +01:00
Kamran Ahmed
60568caff7 Add table of contents to guides 2024-04-03 17:25:23 +01:00
19 changed files with 310 additions and 68 deletions

72
.github/workflows/rsync-ssr.yml vendored Normal file
View File

@@ -0,0 +1,72 @@
name: Deploy to EC2
on:
workflow_dispatch: # allow manual run
push:
branches:
- feat/ssr
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 20
- uses: pnpm/action-setup@v3.0.0
with:
version: 8.15.6
# --------------------
# Setup configuration
# --------------------
- name: Prepare configuration files
run: |
git clone https://${{ secrets.GH_PAT }}@github.com/roadmapsh/infra-config.git configuration --depth 1
- name: Copy configuration files
run: |
cp configuration/dist/github/developer-roadmap.env .env
# --------------------
# Prepare the build
# --------------------
- name: Install dependencies
run: |
pnpm install
- name: Generate build
run: |
git clone https://${{ secrets.GH_PAT }}@github.com/roadmapsh/web-draw.git .temp/web-draw --depth 1
npm run generate-renderer
npm run build
# --------------------
# Deploy to EC2
# --------------------
- uses: webfactory/ssh-agent@v0.7.0
with:
ssh-private-key: ${{ secrets.EC2_PRIVATE_KEY }}
- name: Deploy app to EC2
run: |
rsync -avz --omit-dir-times --exclude ".git" --exclude "configuration" -e "ssh -o StrictHostKeyChecking=no" -p ./ ${{ secrets.EC2_USERNAME }}@${{ secrets.EC2_HOST }}:/var/www/v2.roadmap.sh/
- name: Restart PM2
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_PRIVATE_KEY }}
script: |
cd /var/www/v2.roadmap.sh
sudo pm2 restart web-roadmap
# --------------------
# Clear Cloudfront Caching
# --------------------
- name: Clear Cloudfront Caching
run: |
curl -L \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.GH_PAT }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/roadmapsh/infra-ansible/actions/workflows/playbook.yml/dispatches \
-d '{ "ref":"master", "inputs": { "playbook": "roadmap_web.yml", "tags": "cloudfront", "is_verbose": false } }'

View File

@@ -246,7 +246,7 @@ export function AITermSuggestionInput(props: AITermSuggestionInputProps) {
key={result?._id}
type="button"
className={cn(
'flex w-full items-center rounded p-2 text-sm',
'flex w-full items-start rounded p-2 text-left text-sm',
counter === activeCounter ? 'bg-gray-100' : '',
)}
onMouseOver={() => setActiveCounter(counter)}
@@ -264,7 +264,7 @@ export function AITermSuggestionInput(props: AITermSuggestionInputProps) {
>
<span
className={cn(
'mr-2 rounded-full p-1 px-1.5 text-xs leading-none',
'mr-2 whitespace-nowrap rounded-full p-1 px-1.5 text-xs leading-none',
result.isOfficial
? 'bg-green-500 text-green-50'
: 'bg-blue-400 text-blue-50',

View File

@@ -113,7 +113,9 @@ export function GenerateRoadmap() {
const [roadmapTopicLimitUsed, setRoadmapTopicLimitUsed] = useState(0);
const [isConfiguring, setIsConfiguring] = useState(false);
const [openAPIKey, setOpenAPIKey] = useState<string | undefined>(getOpenAIKey());
const [openAPIKey, setOpenAPIKey] = useState<string | undefined>(
getOpenAIKey(),
);
const isKeyOnly = IS_KEY_ONLY_ROADMAP_GENERATION;
const isAuthenticatedUser = isLoggedIn();
@@ -658,7 +660,7 @@ export function GenerateRoadmap() {
</div>
<div
className={cn({
'relative mb-20 max-h-[800px] min-h-[800px] sm:max-h-[1000px] md:min-h-[1000px] lg:max-h-[1200px] lg:min-h-[1200px] overflow-hidden':
'relative mb-20 max-h-[800px] min-h-[800px] overflow-hidden sm:max-h-[1000px] md:min-h-[1000px] lg:max-h-[1200px] lg:min-h-[1200px]':
!isAuthenticatedUser,
})}
>
@@ -666,18 +668,18 @@ export function GenerateRoadmap() {
ref={roadmapContainerRef}
id="roadmap-container"
onClick={handleNodeClick}
className="relative px-4 py-5 [&>svg]:mx-auto [&>svg]:max-w-[1300px]"
className="relative min-h-[400px] px-4 py-5 [&>svg]:mx-auto [&>svg]:max-w-[1300px]"
/>
{!isAuthenticatedUser && (
<div className="absolute bottom-0 left-0 right-0">
<div className="h-80 w-full bg-gradient-to-t from-gray-100 to-transparent" />
<div className="bg-gray-100">
<div className="mx-auto px-5 max-w-[600px] flex-col items-center justify-center bg-gray-100 pt-px">
<div className="mx-auto max-w-[600px] flex-col items-center justify-center bg-gray-100 px-5 pt-px">
<div className="mt-8 text-center">
<h2 className="mb-0.5 sm:mb-3 text-xl sm:text-2xl font-medium">
<h2 className="mb-0.5 text-xl font-medium sm:mb-3 sm:text-2xl">
Sign up to View the full roadmap
</h2>
<p className="mb-6 text-sm sm:text-base text-gray-600 text-balance">
<p className="mb-6 text-balance text-sm text-gray-600 sm:text-base">
You must be logged in to view the complete roadmap
</p>
</div>

View File

@@ -45,7 +45,7 @@ export function RoadmapSearch(props: RoadmapSearchProps) {
const randomTerms = ['OAuth', 'APIs', 'UX Design', 'gRPC'];
return (
<div className="flex flex-grow flex-col items-center px-4 py-6 sm:px-6">
<div className="flex flex-grow flex-col items-center px-4 py-6 sm:px-6 md:my-24 lg:my-32">
{isConfiguring && (
<IncreaseRoadmapLimit
onClose={() => {
@@ -55,7 +55,7 @@ export function RoadmapSearch(props: RoadmapSearchProps) {
}}
/>
)}
<div className="flex flex-col gap-0 text-center sm:gap-2 md:mt-24 lg:mt-32">
<div className="flex flex-col gap-0 text-center sm:gap-2">
<h1 className="relative text-2xl font-medium sm:text-3xl">
<span className="hidden sm:inline">Generate roadmaps with AI</span>
<span className="inline sm:hidden">AI Roadmap Generator</span>

View File

@@ -0,0 +1,61 @@
---
import { getGuideTableOfContent, type GuideFileType } from '../../lib/guide';
import MarkdownFile from '../MarkdownFile.astro';
import { TableOfContent } from '../TableOfContent/TableOfContent';
interface Props {
guide: GuideFileType;
}
const { guide } = Astro.props;
const allHeadings = guide.getHeadings();
const tableOfContent = getGuideTableOfContent(allHeadings);
const showTableOfContent = tableOfContent.length > 0;
const { frontmatter: guideFrontmatter, author } = guide;
---
<article class='lg:grid lg:max-w-full lg:grid-cols-[1fr_minmax(0,700px)_1fr]'>
{
showTableOfContent && (
<div class='bg-gradient-to-r from-gray-50 py-0 lg:col-start-3 lg:col-end-4 lg:row-start-1'>
<TableOfContent toc={tableOfContent} client:load />
</div>
)
}
<div
class:list={['col-start-2 col-end-3 row-start-1 mx-auto max-w-[700px] py-5 sm:py-10', {
'lg:border-r': showTableOfContent
}]}
>
<MarkdownFile>
<h1 class='text-balance text-4xl mb-3 font-bold'>{guideFrontmatter.title}</h1>
<p
class='flex items-center justify-start text-sm text-gray-400 my-0'
>
<a
href={`/authors/${author.id}`}
class='inline-flex items-center font-medium hover:text-gray-600 hover:underline underline-offset-2'
>
<img
alt={author.frontmatter.name}
src={author.frontmatter.imageUrl}
class='mb-0 mr-2 inline h-5 w-5 rounded-full'
/>
{author.frontmatter.name}
</a>
<span class='mx-2 hidden sm:inline'>&middot;</span>
<a
class='hover:text-gray-600 underline-offset-2 hidden sm:inline'
href={`https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data/guides/${guide.id}.md`}
target='_blank'
>
Improve this Guide
</a>
</p>
<guide.Content />
</MarkdownFile>
</div>
</article>

View File

@@ -7,6 +7,8 @@ export interface Props {
const { guide } = Astro.props;
const { frontmatter, author } = guide;
return undefined;
---
<div class='border-b bg-white py-5 sm:py-12'>

View File

@@ -1,5 +1,5 @@
<div
class='prose-xl prose-blockquote:font-normal prose container prose-code:bg-transparent prose-h2:text-3xl prose-h2:mt-10 prose-h2:mb-3 prose-h5:font-medium prose-h3:mt-2 prose-img:mt-1'
class='container prose-h2:text-balance prose-h3:text-balance prose-h4:text-balance prose-h5:text-balance prose prose-xl prose-h2:mb-3 prose-h2:mt-10 prose-h2:scroll-mt-5 prose-h2:text-3xl prose-h3:mt-2 prose-h3:scroll-mt-5 prose-h5:font-medium prose-blockquote:font-normal prose-code:bg-transparent prose-img:mt-1 prose-h2:sm:scroll-mt-10 prose-h3:sm:scroll-mt-10'
>
<slot />
</div>

View File

@@ -17,12 +17,10 @@ import { AccountDropdown } from './AccountDropdown';
</a>
<a
target='_blank'
rel='noreferrer nofollow'
href='https://boards.greenhouse.io/insightmediagroupllc/jobs/4002116008'
href='/ai'
class='group inline sm:hidden relative !mr-2 text-blue-300 hover:text-white'
>
We're Hiring
AI Roadmaps&nbsp;
<span class='absolute -right-[11px] top-0'>
<span class='relative flex h-2 w-2'>
@@ -43,12 +41,10 @@ import { AccountDropdown } from './AccountDropdown';
</a>
<a href='/teams' class='text-gray-400 hover:text-white'> Teams</a>
<a
target='_blank'
rel='noreferrer nofollow'
href='https://boards.greenhouse.io/insightmediagroupllc/jobs/4002116008'
href='/ai'
class='group relative !mr-2 text-blue-300 hover:text-white'
>
We're Hiring
AI Roadmaps
<span class='absolute -right-[11px] top-0'>
<span class='relative flex h-2 w-2'>

View File

@@ -0,0 +1,98 @@
import { useState, type CSSProperties } from 'react';
import type { HeadingGroupType } from '../../lib/guide';
import { ChevronDown } from 'lucide-react';
import { cn } from '../../lib/classname';
type TableOfContentProps = {
toc: HeadingGroupType[];
};
export function TableOfContent(props: TableOfContentProps) {
const { toc } = props;
const [isOpen, setIsOpen] = useState(false);
if (toc.length === 0) {
return null;
}
const totalRows = toc.flatMap((heading) => {
return [heading, ...heading.children];
}).length;
return (
<div
className={cn(
'relative min-w-[250px] px-5 pt-0 max-lg:min-w-full max-lg:max-w-full max-lg:border-none max-lg:px-0 lg:pt-10',
{
'top-0 lg:!sticky': totalRows <= 20,
},
)}
>
<h4 className="text-lg font-medium max-lg:hidden">In this article</h4>
<button
className="flex w-full items-center justify-between gap-2 bg-gray-300 px-3 py-2 text-sm font-medium lg:hidden"
onClick={() => setIsOpen(!isOpen)}
>
Table of Contents
<ChevronDown
size={16}
className={cn(
'transform transition-transform',
isOpen && 'rotate-180',
)}
/>
</button>
<ol
className={cn(
'mt-0.5 max-lg:absolute max-lg:top-full max-lg:mt-0 max-lg:w-full space-y-0 max-lg:bg-white max-lg:shadow',
!isOpen && 'hidden lg:block',
isOpen && 'block',
)}
>
{toc.map((heading) => (
<li key={heading.slug}>
<a
href={`#${heading.slug}`}
className="text-sm text-gray-500 no-underline hover:text-black max-lg:block max-lg:border-b max-lg:px-3 max-lg:py-1"
onClick={() => {
if (!isOpen) {
return;
}
setIsOpen(false);
}}
>
{heading.text}
</a>
{heading.children.length > 0 && (
<ol className="my-0 ml-4 mt-1 max-lg:ml-0 max-lg:mt-0 max-lg:list-none space-y-0">
{heading.children.map((children) => {
return (
<li key={children.slug}>
<a
href={`#${children.slug}`}
className="text-sm text-gray-500 no-underline hover:text-black max-lg:block max-lg:border-b max-lg:px-3 max-lg:py-1 max-lg:pl-8"
onClick={() => {
if (!isOpen) {
return;
}
setIsOpen(false);
}}
>
{children.text}
</a>
</li>
);
})}
</ol>
)}
</li>
))}
</ol>
</div>
);
}

View File

@@ -299,7 +299,7 @@ Lets take a look at some pros and cons for the last programming language on o
- Gos ecosystem is quite young when compared to the other alternatives here, so the maturity of the tools available might not be the same as, for example, Java or JavaScript tooling.
### Choosing the Ideal Backend Language
## Choosing the Ideal Backend Language
So, are these the best backend programming languages out there? Is there an absolute “best” backend programming language?
@@ -326,13 +326,13 @@ A handy tool when trying to evaluate a language like that is [roadmap.sh](https:
There youll find community-maintained roadmaps for many career paths within software development. In particular, for this article, the [backend roadmap](https://roadmap.sh/backend) is a great place to start, because while picking a backend language is important, youll see there that its not just about the language. In fact, there is a lot of tech around the language that is also required (Im referring to databases, git, understanding how client-server communication works, and a big “etc).
### Jumpstarting Your Backend Development Journey
## Jumpstarting Your Backend Development Journey
To get started with your backend development journey, it's crucial to have a roadmap that guides you through the learning process and equips you with the skills to build robust and scalable backend systems.
Lucky for you, if youre reading this, that means youve found the most complete and comprehensive roadmap online: [roadmap.sh](https://roadmap.sh), the current [backend roadmap](https://roadmap.sh/backend) is filled with details of everything you should and could (optionally) learn in your journey to becoming a backend developer.
### Guided Learning: From Online Courses to Bootcamps
## Guided Learning: From Online Courses to Bootcamps
Online courses and bootcamps serve as invaluable companions on your learning expedition. Platforms like Udemy, Coursera, and freeCodeCamp offer comprehensive backend development courses.
@@ -340,13 +340,13 @@ These resources not only cover programming languages like Python, Java, or JavaS
Whatever choice you go for, make sure youre not following trends or just copying the learning methods of others. Learning is a very personal experience and what works for others might not work for you, and vice versa. So make sure to do the proper research and figure out what option works best for you.
### Building Community Connections for Learning Support
## Building Community Connections for Learning Support
Joining developer communities (there are several on Twitter for example), forums like Stack Overflow, or participating in social media groups dedicated to backend development creates a network of support.
Engaging with experienced developers, sharing challenges, and seeking advice fosters a collaborative learning environment. Attend local meetups or virtual events if you can to connect with professionals in the field, gaining insights and building relationships that can prove invaluable throughout your journey.
### Think about you and your project
## Think about you and your project
There are many ways to go about picking the ideal backend language for you. If there is anything you should take home with you after reading this article, it is that most languages are equivalent in the sense that youll be able to do pretty much everything with any of them.
@@ -359,7 +359,7 @@ The questions you should also be asking yourself are:
In the end, personal preference and actual project requirements (if you have any) are very important, because both will influence how much you enjoy (or dont enjoy) the learning process.
### Crafting a Portfolio to Display Your Backend Skills:
## Crafting a Portfolio to Display Your Backend Skills:
As you accumulate skills and knowledge, showcase your journey through a well-crafted portfolio. Include projects that highlight your backend skills, demonstrating your ability to - design databases, implement server-side logic, and integrate with client side technologies. Whether it's a dynamic web application, a RESTful API, or a data-driven project, your portfolio becomes a tangible representation of your backend development capabilities for potential employers or collaborators.
@@ -367,7 +367,7 @@ When it comes to deciding where to publish this portfolio, you have some options
In the end, the important thing is that you should be sharing your experience somewhere, especially when you dont have working experience in the field.
### Conclusion
## Conclusion
In the end, there are many backend programming languages to choose from, and what language you go for, is up to you and your particular context/needs. All I can do is guide you to the door, but you have to cross it yourself. Some interesting options are:

View File

@@ -1,4 +1,4 @@
# Chat I/O
# Chatty I/O
The cumulative effect of a large number of I/O requests can have a significant impact on performance and responsiveness.

View File

@@ -87,3 +87,33 @@ export async function getGuideById(
return allGuides.find((guide) => guide.id === id);
}
type HeadingType = ReturnType<MarkdownFileType['getHeadings']>[number];
export type HeadingGroupType = HeadingType & { children: HeadingType[] };
const NUMBERED_LIST_REGEX = /^\d+\.\s+?/;
export function getGuideTableOfContent(headings: HeadingType[]) {
const tableOfContents: HeadingGroupType[] = [];
let currentGroup: HeadingGroupType | null = null;
headings
.filter((heading) => heading.depth !== 1)
.forEach((heading) => {
if (heading.depth === 2) {
currentGroup = {
...heading,
text: heading.text.replace(NUMBERED_LIST_REGEX, ''),
children: [],
};
tableOfContents.push(currentGroup);
} else if (currentGroup && heading.depth === 3) {
currentGroup.children.push({
...heading,
text: heading.text.replace(NUMBERED_LIST_REGEX, ''),
});
}
});
return tableOfContents;
}

View File

@@ -4,8 +4,12 @@ import { ActivityPage } from '../../components/Activity/ActivityPage';
import AccountLayout from '../../layouts/AccountLayout.astro';
---
<AccountLayout title='Update Profile' noIndex={true} initialLoadingMessage={'Loading activity'}>
<AccountLayout
title='Activity'
noIndex={true}
initialLoadingMessage={'Loading activity'}
>
<AccountSidebar activePageId='activity' activePageTitle='Activity'>
<ActivityPage client:only="react" />
<ActivityPage client:only='react' />
</AccountSidebar>
</AccountLayout>

View File

@@ -1,9 +1,8 @@
---
import LoginPopup from '../../components/AuthenticationFlow/LoginPopup.astro';
import { ExploreAIRoadmap } from '../../components/ExploreAIRoadmap/ExploreAIRoadmap';
import AccountLayout from '../../layouts/AccountLayout.astro';
import BaseLayout from '../../layouts/BaseLayout.astro';
---
<AccountLayout title='Explore AI Generated Roadmaps'>
<BaseLayout title='Explore AI Generated Roadmaps'>
<ExploreAIRoadmap client:load />
</AccountLayout>
</BaseLayout>

View File

@@ -1,10 +1,8 @@
---
import LoginPopup from '../../components/AuthenticationFlow/LoginPopup.astro';
import { GenerateRoadmap } from '../../components/GenerateRoadmap/GenerateRoadmap';
import AccountLayout from '../../layouts/AccountLayout.astro';
import BaseLayout from '../../layouts/BaseLayout.astro';
---
<AccountLayout title='Roadmap AI'>
<BaseLayout title='Roadmap AI'>
<GenerateRoadmap client:load />
<LoginPopup />
</AccountLayout>
</BaseLayout>

View File

@@ -1,6 +1,6 @@
---
import GuideContent from '../../components/Guide/GuideContent.astro';
import GuideHeader from '../../components/GuideHeader.astro';
import MarkdownFile from '../../components/MarkdownFile.astro';
import BaseLayout from '../../layouts/BaseLayout.astro';
import { getGuideById } from '../../lib/guide';
import { getOpenGraphImageUrl } from '../../lib/open-graph';
@@ -23,11 +23,6 @@ const ogImageUrl = getOpenGraphImageUrl({
canonicalUrl={guideData.canonicalUrl}
ogImageUrl={ogImageUrl}
>
<GuideHeader guide={guide} />
<div class='mx-auto max-w-[700px] py-5 sm:py-10'>
<MarkdownFile>
<guide.Content />
</MarkdownFile>
</div>
<GuideHeader guide={guide!} />
<GuideContent guide={guide!} />
</BaseLayout>

View File

@@ -1,6 +1,6 @@
---
import GuideContent from '../../components/Guide/GuideContent.astro';
import GuideHeader from '../../components/GuideHeader.astro';
import MarkdownFile from '../../components/MarkdownFile.astro';
import BaseLayout from '../../layouts/BaseLayout.astro';
import { getGuideById } from '../../lib/guide';
import { getOpenGraphImageUrl } from '../../lib/open-graph';
@@ -23,11 +23,6 @@ const ogImageUrl = getOpenGraphImageUrl({
canonicalUrl={guideData.canonicalUrl}
ogImageUrl={ogImageUrl}
>
<GuideHeader guide={guide} />
<div class='mx-auto max-w-[700px] py-5 sm:py-10'>
<MarkdownFile>
<guide.Content />
</MarkdownFile>
</div>
<GuideHeader guide={guide!} />
<GuideContent guide={guide!} />
</BaseLayout>

View File

@@ -1,6 +1,6 @@
---
import GuideContent from '../../components/Guide/GuideContent.astro';
import GuideHeader from '../../components/GuideHeader.astro';
import MarkdownFile from '../../components/MarkdownFile.astro';
import BaseLayout from '../../layouts/BaseLayout.astro';
import { getGuideById } from '../../lib/guide';
import { getOpenGraphImageUrl } from '../../lib/open-graph';
@@ -24,10 +24,5 @@ const ogImageUrl = getOpenGraphImageUrl({
ogImageUrl={ogImageUrl}
>
<GuideHeader guide={guide!} />
<div class='mx-auto max-w-[700px] py-5 sm:py-10'>
<MarkdownFile>
<guide.Content />
</MarkdownFile>
</div>
<GuideContent guide={guide!} />
</BaseLayout>

View File

@@ -1,6 +1,6 @@
---
import GuideContent from '../../components/Guide/GuideContent.astro';
import GuideHeader from '../../components/GuideHeader.astro';
import MarkdownFile from '../../components/MarkdownFile.astro';
import BaseLayout from '../../layouts/BaseLayout.astro';
import { getAllGuides, type GuideFileType } from '../../lib/guide';
import { getOpenGraphImageUrl } from '../../lib/open-graph';
@@ -38,10 +38,5 @@ const ogImageUrl = getOpenGraphImageUrl({
ogImageUrl={ogImageUrl}
>
<GuideHeader guide={guide} />
<div class='mx-auto max-w-[700px] py-5 sm:py-10'>
<MarkdownFile>
<guide.Content />
</MarkdownFile>
</div>
<GuideContent guide={guide!} />
</BaseLayout>