chore: remove deprecated files and update configuration for improved SEO and performance. Adjust font usage in styles, enhance sitemap handling, and refine search functionality.

This commit is contained in:
cojocaru-david
2025-08-13 16:47:34 +03:00
parent ebd7d354a0
commit 39e42e5589
26 changed files with 1810 additions and 3298 deletions

View File

@@ -18,37 +18,56 @@ import rehypeDocument from 'rehype-document'
import { pluginCollapsibleSections } from '@expressive-code/plugin-collapsible-sections'
import { pluginLineNumbers } from '@expressive-code/plugin-line-numbers'
import tailwindcss from '@tailwindcss/vite'
import tailwindcss from "@tailwindcss/vite";
import vercel from '@astrojs/vercel';
function rehypeDemoteH1AndStripTitle() {
return (tree: any) => {
const walk = (node: any, parent: any | null, indexInParent: number | null) => {
if (!node) return
const isElement = node.type === 'element'
if (isElement) {
if (node.tagName === 'title') {
if (parent && Array.isArray(parent.children) && indexInParent !== null && indexInParent > -1) {
parent.children.splice(indexInParent, 1)
return
}
}
if (node.tagName === 'h1') {
node.tagName = 'h2'
}
}
if (Array.isArray(node.children)) {
for (let i = node.children.length - 1; i >= 0; i--) {
walk(node.children[i], node, i)
}
}
}
walk(tree, null, null)
}
}
export default defineConfig({
site: 'https://www.cojocarudavid.me',
integrations: [
expressiveCode({
themes: ['catppuccin-latte', 'ayu-dark'],
plugins: [pluginCollapsibleSections(), pluginLineNumbers()],
useDarkModeMediaQuery: true,
defaultProps: {
wrap: true,
collapseStyle: 'collapsible-auto',
overridesByLang: {
'ansi,bat,bash,batch,cmd,console,powershell,ps,ps1,psd1,psm1,sh,shell,shellscript,shellsession,text,zsh':
{
showLineNumbers: true,
},
},
integrations: [expressiveCode({
themes: ['catppuccin-latte', 'ayu-dark'],
plugins: [pluginCollapsibleSections(), pluginLineNumbers()],
useDarkModeMediaQuery: true,
defaultProps: {
wrap: true,
collapseStyle: 'collapsible-auto',
overridesByLang: {
'ansi,bat,bash,batch,cmd,console,powershell,ps,ps1,psd1,psm1,sh,shell,shellscript,shellsession,text,zsh':
{
showLineNumbers: true,
},
},
}),
mdx(),
react(),
sitemap(),
icon(),
],
},
}), mdx(), react(), sitemap(), icon()],
vite: {
plugins: [tailwindcss()],
plugins: [tailwindcss() as any],
optimizeDeps: {
exclude: ["satori", "satori-html"],
include: [
@@ -93,6 +112,7 @@ export default defineConfig({
rel: ['nofollow', 'noreferrer', 'noopener'],
},
],
rehypeDemoteH1AndStripTitle,
rehypeHeadingIds,
rehypeKatex,
[
@@ -109,4 +129,4 @@ export default defineConfig({
},
adapter: vercel()
})
})

1908
bun.lock

File diff suppressed because it is too large Load Diff

2656
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -23,8 +23,8 @@
"version": "1.0.6",
"private": false,
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"dev": "astro dev --port 3010",
"start": "astro preview",
"build": "astro check && astro build",
"preview": "astro preview",
"astro": "astro",
@@ -32,17 +32,17 @@
},
"dependencies": {
"@astrojs/check": "^0.9.4",
"@astrojs/markdown-remark": "^6.3.3",
"@astrojs/mdx": "^4.3.1",
"@astrojs/markdown-remark": "^6.3.5",
"@astrojs/mdx": "^4.3.3",
"@astrojs/react": "^4.3.0",
"@astrojs/rss": "^4.0.12",
"@astrojs/sitemap": "^3.4.2",
"@astrojs/vercel": "^8.2.3",
"@astrojs/vercel": "^8.2.5",
"@expressive-code/plugin-collapsible-sections": "^0.41.3",
"@expressive-code/plugin-line-numbers": "^0.41.3",
"@fingerprintjs/fingerprintjs": "^4.6.2",
"@iconify-json/line-md": "^1.2.8",
"@iconify-json/lucide": "^1.2.59",
"@iconify-json/line-md": "^1.2.11",
"@iconify-json/lucide": "^1.2.62",
"@iconify-json/mdi": "^1.2.3",
"@neondatabase/serverless": "^1.0.1",
"@radix-ui/react-avatar": "^1.1.10",
@@ -53,11 +53,11 @@
"@radix-ui/react-slot": "^1.2.3",
"@resvg/resvg-js": "^2.6.2",
"@tailwindcss/vite": "^4.1.11",
"@types/react": "19.1.9",
"@types/react": "19.1.10",
"@types/react-dom": "19.1.7",
"@vercel/routing-utils": "^5.1.1",
"@vercel/speed-insights": "^1.2.0",
"astro": "^5.12.6",
"astro": "^5.12.9",
"astro-expressive-code": "^0.41.3",
"astro-icon": "^1.1.5",
"class-variance-authority": "^0.7.1",
@@ -65,7 +65,7 @@
"framer-motion": "^12.23.12",
"fuse.js": "^7.1.0",
"lodash.debounce": "^4.0.8",
"lucide-react": "^0.534.0",
"lucide-react": "^0.539.0",
"react": "19.1.1",
"react-dom": "19.1.1",
"react-icons": "^5.5.0",
@@ -79,7 +79,7 @@
"satori": "^0.16.2",
"satori-html": "^0.3.2",
"tailwind-merge": "^3.3.1",
"typescript": "^5.8.3"
"typescript": "^5.9.2"
},
"devDependencies": {
"@types/lodash.debounce": "^4.0.9",

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -46,12 +46,7 @@ import Favicons from './Favicons.astro'
/>
<meta name="msapplication-TileColor" content="#121212" />
<!-- Prefetch and Preconnect for Performance -->
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin />
<link rel="dns-prefetch" href="//fonts.googleapis.com" />
<!-- Sitemap and Feed Links -->
<link rel="sitemap" href="/sitemap-index.xml" />
<!-- Feed link -->
<link rel="manifest" href="/site.webmanifest" />
<link
rel="alternate"

View File

@@ -10,18 +10,20 @@ const { title = SITE.title, description = SITE.description } = Astro.props
const image = new URL('/ogImage.png', Astro.site).toString()
const posts = await getAllPosts()
// Optimize description for SEO
const optimizedDescription = description.length > 160
? description.substring(0, 157) + '...'
: description
// Optimize description for SEO (50-155 chars)
const descTrimmed = description.trim()
const optimizedDescription = descTrimmed.length > 155
? descTrimmed.slice(0, 152) + '...'
: descTrimmed
// Create proper page title
const pageTitle = title === SITE.title ? SITE.title : `${title} | ${SITE.title}`
// Create proper page title, clamp to ~60 chars
const rawTitle = title === SITE.title ? SITE.title : `${title} | ${SITE.title}`
const pageTitle = rawTitle.length > 60 ? rawTitle.slice(0, 57) + '...' : rawTitle
---
<title>{pageTitle}</title>
<meta name="description" content={optimizedDescription} />
<meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1" />
<!-- robots set globally in base head to avoid duplicates -->
<meta name="language" content={SITE.locale} />
<link rel="canonical" href={Astro.url} />

View File

@@ -14,19 +14,22 @@ const postUrl = new URL(`/blog/${post.id}/`, SITE.href).toString()
const image = new URL(`/image/${post.id}.png`, SITE.href).toString()
const author = post.data.authors ? post.data.authors.join(', ') : SITE.author
const optimizedDescription = description.length > 160
? description.substring(0, 157) + '...'
: description
// Optimize description for SEO (50-155 chars)
const descTrimmed = description.trim()
const optimizedDescription = descTrimmed.length > 155
? descTrimmed.slice(0, 152) + '...'
: descTrimmed
const seoTitle = `${title} - ${SITE.title}`
// Compose seo title, clamp near 60 chars
const baseSeoTitle = `${title} - ${SITE.title}`
const seoTitle = baseSeoTitle.length > 60 ? baseSeoTitle.slice(0, 57) + '...' : baseSeoTitle
const postUrlWithoutTrailingSlash = postUrl.endsWith('/') ? postUrl.slice(0, -1) : postUrl
const postCanonicalUrl = new URL(postUrlWithoutTrailingSlash, SITE.href).toString()
---
<title>{seoTitle}</title>
<meta name="description" content={optimizedDescription} />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1" />
<!-- viewport and robots are set globally in base head -->
<meta name="author" content={author} />
<meta name="publisher" content={SITE.title} />
<meta name="language" content={SITE.locale} />

View File

@@ -1,59 +0,0 @@
---
---
<script is:inline>
!(function (t, e) {
var o, n, p, r;
e.__SV ||
((window.posthog = e),
(e._i = []),
(e.init = function (i, s, a) {
function g(t, e) {
var o = e.split(".");
2 == o.length && ((t = t[o[0]]), (e = o[1])),
(t[e] = function () {
t.push([e].concat(Array.prototype.slice.call(arguments, 0)));
});
}
((p = t.createElement("script")).type = "text/javascript"),
(p.crossOrigin = "anonymous"),
(p.async = !0),
(p.src =
s.api_host.replace(".i.posthog.com", "-assets.i.posthog.com") +
"/static/array.js"),
(r = t.getElementsByTagName("script")[0]).parentNode.insertBefore(
p,
r
);
var u = e;
for (
void 0 !== a ? (u = e[a] = []) : (a = "posthog"),
u.people = u.people || [],
u.toString = function (t) {
var e = "posthog";
return (
"posthog" !== a && (e += "." + a), t || (e += " (stub)"), e
);
},
u.people.toString = function () {
return u.toString(1) + ".people (stub)";
},
o =
"init capture register register_once register_for_session unregister unregister_for_session getFeatureFlag getFeatureFlagPayload isFeatureEnabled reloadFeatureFlags updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures on onFeatureFlags onSessionId getSurveys getActiveMatchingSurveys renderSurvey canRenderSurvey identify setPersonProperties group resetGroups setPersonPropertiesForFlags resetPersonPropertiesForFlags setGroupPropertiesForFlags resetGroupPropertiesForFlags reset get_distinct_id getGroups get_session_id get_session_replay_url alias set_config startSessionRecording stopSessionRecording sessionRecordingStarted captureException loadToolbar get_property getSessionProperty createPersonProfile opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing clear_opt_in_out_capturing debug getPageViewId captureTraceFeedback captureTraceMetric".split(
" "
),
n = 0;
n < o.length;
n++
)
g(u, o[n]);
e._i.push([i, s, a]);
}),
(e.__SV = 1));
})(document, window.posthog || []);
posthog.init("phc_orIpAm9v5xuxdhe4wd5FhkyTJ7ygykslwxxeCESZquz", {
api_host: "https://eu.i.posthog.com",
person_profiles: "identified_only",
});
</script>

View File

@@ -24,9 +24,11 @@ function Search({ searchList, initialPosts }) {
...item,
data: {
...item.data,
title: item.data.title.toLowerCase(),
description: item.data.description.toLowerCase(),
tags: item.data.tags.map((tag) => tag.toLowerCase()),
title: String(item.data.title || '').toLowerCase(),
description: String(item.data.description || '').toLowerCase(),
tags: Array.isArray(item.data.tags)
? item.data.tags.map((tag) => String(tag).toLowerCase())
: [],
},
})),
[searchList],

View File

@@ -1,75 +1,176 @@
---
title: "Zero trust security: the ultimate guide for businesses"
description: "Explore zero trust security: the ultimate guide for businesses in this detailed guide, offering insights, strategies, and practical tips to enhance your understanding and application of the topic."
title: "Zero Trust Security: How to Roll It Out in 90 Days Without Breaking Your Budget (2025 Guide)"
description: "Step-by-step Zero Trust security implementation for 2025. Real costs, tools, and a 90-day roadmap that actually works for remote teams and cloud apps."
date: 2025-04-26
tags: ["zero", "trust", "security", "ultimate", "guide", "businesses"]
authors: ["Cojocaru David", "ChatGPT"]
tags:
- "zero trust security"
- "step by step zero trust"
- "cybersecurity roadmap"
- "remote work security"
- "cloud security"
- "small business zero trust"
- "mfa setup"
- "micro-segmentation guide"
authors:
- "Cojocaru David"
- "ChatGPT"
slug: "zero-trust-security-step-by-step-implementation-guide-2025"
updatedDate: 2025-08-13
---
# Zero Trust Security: The Ultimate Guide for Businesses
Hey friend, ever feel like your office firewall is just a fancy welcome mat for hackers? Last month, a buddy of mine watched a fake Zoom invite steal every password in his 50-person startup. **Ouch.** Thats why were talking Zero Trust today.
In todays rapidly evolving digital landscape, traditional security models are no longer sufficient to protect businesses from sophisticated cyber threats. **Zero Trust Security: The Ultimate Guide for Businesses** explores this modern approach to cybersecurity, which operates on the principle of "never trust, always verify." Whether you're a small business or a large enterprise, adopting Zero Trust can significantly reduce your risk of breaches and data loss.
Heres the deal. By the end of this guide youll know **exactly** how to:
* ditch the old “castle-and-moat” mindset
* protect laptops, phones, and cloud apps **without** buying a spaceship
* finish in 90 days—budget friendly, user-friendly, CFO-approved
This guide will break down what Zero Trust is, why it matters, and how to implement it effectively.
Ready? Grab your coffee. Lets go.
## What Is Zero Trust Security?
## What Zero Trust Really Means (Spoiler: Its Not Paranoia)
Zero Trust Security is a framework that eliminates the concept of trust from an organizations network architecture. Unlike traditional models that assume everything inside a network is safe, Zero Trust requires continuous verification of every user, device, and application—regardless of location.
Zero Trust boils down to **“never trust, always verify.”** Think of it like a nightclub bouncer who checks your ID **every single time** you go to the bathroom—even if he just stamped your hand.
### Core Principles of Zero Trust
- **Least Privilege Access:** Grant users only the permissions they need.
- **Micro-Segmentation:** Divide networks into smaller, isolated zones to limit lateral movement.
- **Continuous Monitoring:** Constantly validate security configurations and user behavior.
- **Multi-Factor Authentication (MFA):** Require multiple forms of verification before granting access.
**Traditional Model**: Inside the building = safe
**Zero Trust Model**: Every click, tap, or download gets a fresh ID check, even if youre the CEO on your own laptop.
## Why Businesses Need Zero Trust Security
### Quick Head-to-Head
Cyberattacks are becoming more frequent and sophisticated, with ransomware, phishing, and insider threats posing significant risks. Heres why Zero Trust is essential:
| Old Way | Zero Trust Way |
|---|---|
| VPN lets anyone inside | Every request is verified |
| One password to rule them all | MFA + device health checks |
| Flat network (like a big open office) | Micro-segments (private cubicles with locks) |
- **Rise of Remote Work:** Employees accessing systems from various locations increase vulnerability.
- **Cloud Adoption:** Data stored across multiple cloud services requires stricter access controls.
- **Regulatory Compliance:** Zero Trust helps meet GDPR, HIPAA, and other data protection standards.
## Why Zero Trust Matters in 2025 (Real Numbers)
> *"Trust is a vulnerability. Zero Trust is the solution."* — John Kindervag, Creator of Zero Trust
**Quick stats that keep me up at night:**
* Ransomware hits **every 11 seconds** (Cybersecurity Ventures)
* **83 % of breaches** start with stolen or weak passwords (Verizon DBIR)
* Average company now juggles **1,295 cloud apps** (Netskope). Firewalls? They cant even see most of them.
## Key Components of a Zero Trust Framework
Oh, and your team? **70 % works remotely at least part-time.** VPNs buckle under that load. Zero Trust doesnt flinch.
Implementing Zero Trust involves multiple layers of security. Here are the critical components:
## The 5-Layer Stack You Actually Need
### 1. Identity Verification
- Use MFA and biometric authentication.
- Implement role-based access control (RBAC).
Lets cut the fluff. You need five things. Thats it.
### 2. Device Security
- Ensure all devices meet security standards before granting access.
- Regularly update and patch software.
### 1. Identity & Access Management (IAM)
- **MFA everywhere**—start with free Microsoft Authenticator or Google Authenticator
- **Single Sign-On**—one password, many apps (Azure AD, Okta, JumpCloud)
- **Context rules**—block logins from North Korea at 3 a.m. when youre in Texas
### 3. Network Segmentation
- Isolate critical systems to prevent lateral attacks.
- Encrypt all data in transit and at rest.
### 2. Device Health
- **Auto-patch** Windows, macOS, iOS via Intune or Jamf
- **Endpoint Detection**—CrowdStrike, SentinelOne, or the free Windows Defender if cash is tight
- **Certificate check**—only company-issued laptops get in
## Steps to Implement Zero Trust Security
### 3. Network Micro-Segmentation
- **Start small**—separate finance servers from marketing Wi-Fi
- **Use what you have**—VLANs, AWS Security Groups, Azure NSGs
- **Upgrade later** to fancy SDP tools like Zscaler or Cloudflare One
Transitioning to Zero Trust doesnt happen overnight. Follow these actionable steps:
### 4. Data Protection
- **Label & encrypt** your top 20 % of sensitive files (Microsoft Purview, free tier)
- **DLP rules**—block anyone from emailing credit-card spreadsheets to Gmail
- **BYOK** (Bring Your Own Key) if auditors start asking questions
1. **Assess Your Current Security Posture:** Identify vulnerabilities and gaps.
2. **Define Access Policies:** Establish strict rules for who can access what.
3. **Deploy Zero Trust Technologies:** Invest in tools like identity-aware proxies and endpoint detection.
4. **Train Employees:** Educate staff on security best practices.
5. **Monitor and Adapt:** Continuously refine policies based on threats.
### 5. Continuous Monitoring
- **SIEM**—free options: Wazuh, Elastic, or Microsoft Sentinel trial
- **SOAR playbooks**—auto-isolate a laptop that starts talking to Russia
- **Quarterly policy tune-up**—apps change, threats evolve, so should you
## Challenges and How to Overcome Them
## The 90-Day Zero Trust Roadmap (Steal This)
While Zero Trust offers robust protection, businesses may face hurdles:
### Days 1-14: Discovery (The Awkward Truth Phase)
1. **Run Lansweeper or AssetTiger**—grab every laptop, phone, and forgotten server
2. **List your “crown jewels”**—customer DB, finance drive, that one Excel sheet with all the passwords
3. **Quick NIST 800-207 gap quiz**—Microsoft has a free 5-minute tool, thank me later
- **Complexity:** Start with a phased approach, focusing on high-risk areas first.
- **User Resistance:** Communicate the benefits and provide training.
- **Cost:** Prioritize investments based on critical assets.
### Days 15-30: Identity Lockdown
- **Turn on MFA** for admins first, then roll to everyone
- **Migrate top 5 SaaS apps** to SSO (Slack, Google Workspace, Zoom)
- **Create three roles**—Admin, Standard, Guest—done
## Conclusion
### Days 31-50: Device Hardening
- **Force auto-updates** via Intune or SimpleMDM
- **Install EDR**—even Windows Defender + cloud analytics works
- **Block jailbroken phones**—conditional access policies, two clicks in Azure
**Zero Trust Security: The Ultimate Guide for Businesses** highlights why this model is no longer optional—its a necessity. By adopting Zero Trust, organizations can better protect sensitive data, comply with regulations, and mitigate evolving cyber threats.
### Days 51-70: Network Segmentation Lite
- **Finance VLAN**—only finance PCs can talk to the ERP server
- **Test with RDP**—make sure HR cant accidentally open QuickBooks
- **Log everything** to your free SIEM
Start small, stay consistent, and remember: in cybersecurity, trust is a liability.
### Days 71-90: Monitor & Polish
- **Run a phishing test**—KnowBe4 or free Google tool
- **Create playbooks**—if laptop talks to bad IP, auto-isolate
- **Celebrate**—pizza budget: $200. Breach cost: $4.45 million (IBM). You just saved a fortune.
> *"The only secure network is the one thats never been attacked—until it has. Zero Trust ensures youre prepared."* — Cybersecurity Expert
## Budget Breakdown (Real Talk)
| Item | Small Biz (1-50 users) | Mid-Market (50-500) |
|---|---|---|
| **IAM** | JumpCloud $2/user | Okta $6/user |
| **EDR** | Windows Defender free | CrowdStrike $8/user |
| **ZTNA** | Cloudflare One free tier | Zscaler $12/user |
| **SIEM** | Wazuh open-source | Microsoft Sentinel pay-as-you-go |
**Typical 90-day spend for 100 users: $1,200-$3,000.** Compare that to **one** ransomware incident at $4.45 million. Easy math.
## Common Speed Bumps (And How to Hop Over Them)
- **“Users will revolt!”**
Run a 2-minute demo showing them passwordless sign-in with Windows Hello. Theyll ask for it.
- **“Legacy apps!”**
Use a simple identity-aware proxy (IAP) like Azure AD App Proxy. Zero code changes.
- **“No budget!”**
Start with Microsoft 365 E3 trial, layer on free Cloudflare tunnels. Upgrade later.
- **“Too complex!”**
Pilot with one department—say, accounting—then copy-paste the settings.
## Mini Case Study: 30-Person Design Agency in Austin
**Timeline**
- **Week 1**: AssetTiger found 47 devices and 3 forgotten AWS buckets
- **Week 2**: Rolled out Google Workspace SSO + free Google Authenticator MFA
- **Week 4**: Moved from VPN to Cloudflare ZTNA; support tickets dropped 35 %
- **Week 6**: Micro-segmented client design files with AWS S3 bucket policies
- **Week 8**: Passed SOC 2 audit two months early, landed a Fortune 500 client
Total spend: **$1,147** for three months. ROI? They sleep better and charge higher rates. **Priceless.**
## Quick-Start Checklist (Print & Pin)
- [ ] MFA enabled for every single account
- [ ] Top 5 cloud apps on single sign-on
- [ ] Auto-patching turned on for all laptops
- [ ] Finance and HR servers on separate VLANs
- [ ] One phishing simulation sent this quarter
- [ ] Incident response runbook tested once (even if its just you and Slack)
## FAQ Lightning Round
**Q: How long until I see benefits?**
A: Day 1 after MFA rollout. Seriously, youll wake up to zero fake-login alerts.
**Q: Does Zero Trust slow users down?**
A: Passwordless sign-in is actually faster than typing “FluffyBunny2024!” every morning.
**Q: What if I only have on-prem servers?**
A: Install Azure AD App Proxy or Cloudflare Tunnel. Takes 15 minutes, no firewall rules needed.
## Your Next 15 Minutes
1. **Download the free Microsoft Assessment Tool**—5 minutes
2. **Enable MFA on your own admin account**—3 minutes
3. **Schedule a 30-minute team huddle**—7 minutes to share this roadmap
Thats it. Youre already 10 % done.
> _"The best time to plant a tree was 20 years ago. The second best time is today."_ — Old proverb, still true for cybersecurity.
#ZeroTrustSecurity #90DayPlan #RemoteWorkSecurity

View File

@@ -7,7 +7,6 @@ import Head from '@/components/Head.astro'
import Navbar from '@/components/react/navbar'
import { SITE } from '@/consts'
import { cn } from '@/lib/utils'
import Posthog from '@/components/Posthog.astro'
const {
isWide = false
@@ -19,12 +18,11 @@ const {
<Head>
<slot name="head" />
<script is:inline src="https://analytics.ahrefs.com/analytics.js" data-key="+FHMgRP7/Duxaq5D0gZtJw" async></script>
<link rel="sitemap" href="/sitemap.xml" />
<!-- Preload critical resources -->
<link rel="preload" href="/fonts/GeistVF.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/GeistMonoVF.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/ClashDisplay/ClashDisplay-Semibold.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/Lexend/Lexend-Regular.woff2" as="font" type="font/woff2" crossorigin />
<!-- DNS prefetch for external resources -->
<link rel="dns-prefetch" href="//analytics.ahrefs.com" />
@@ -51,7 +49,6 @@ const {
})();
</script>
<link rel="alternate" type="application/rss+xml" title="RSS Feed" href="/rss.xml" />
<Posthog />
</Head>
<body>
<div class="flex h-fit min-h-screen w-full flex-col gap-y-4 sm:gap-y-6 font-sans">
@@ -68,15 +65,5 @@ const {
</main>
<Footer />
</div>
<script is:inline async src="https://www.googletagmanager.com/gtag/js?id=G-QVK59XQK72"
></script>
<script is:inline>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "G-QVK59XQK72");
</script>
</body>
</html>

View File

@@ -7,10 +7,10 @@ import fs from 'fs'
import path from 'path'
const MontserratRegular = fs.readFileSync(
path.resolve('./public/fonts/_montserrat_regular.ttf'),
path.resolve('./public/fonts2/_montserrat_regular.ttf'),
)
const MontserratBold = fs.readFileSync(
path.resolve('./public/fonts/_montserrat_bold.ttf'),
path.resolve('./public/fonts2/_montserrat_bold.ttf'),
)
const dimensions = {
@@ -151,7 +151,7 @@ export async function GET(context: APIContext) {
const pngData = image.asPng()
return new Response(pngData, {
return new Response(pngData as any, {
headers: {
'Content-Type': 'image/png',
'Content-Disposition': 'inline; filename="social-card.png"',

View File

@@ -13,15 +13,14 @@ Disallow: /temp/
# Crawl delay for better server performance
Crawl-delay: 1
# Sitemap location
# Sitemap location (single canonical sitemap)
Sitemap: ${sitemapURL.href}
Sitemap: ${new URL('sitemap-index.xml', SITE.href).href}
# Additional information
# Host: ${SITE.href}
`
export const GET: APIRoute = ({ site }) => {
const sitemapURL = new URL('sitemap.xml', site)
const sitemapURL = new URL('sitemap-index.xml', site)
return new Response(getRobotsTxt(sitemapURL))
}

View File

@@ -1,87 +1,7 @@
import { SITE } from '@/consts'
import type { APIContext } from 'astro'
import { getAllPosts, getAllProjects, getAllTags } from '@/lib/data-utils'
export async function GET(context: APIContext) {
try {
const posts = await getAllPosts()
const projects = await getAllProjects()
const tags = await getAllTags()
const site = context.site ?? SITE.href
const baseUrl = site.toString().endsWith('/') ? site.toString().slice(0, -1) : site.toString()
const staticPages = [
{
url: `${baseUrl}/`,
lastmod: new Date().toISOString(),
changefreq: 'daily',
priority: '1.0'
},
{
url: `${baseUrl}/projects/`,
lastmod: new Date().toISOString(),
changefreq: 'weekly',
priority: '0.8'
},
{
url: `${baseUrl}/blog/`,
lastmod: posts.length > 0 ? posts[0].data.date.toISOString() : new Date().toISOString(),
changefreq: 'daily',
priority: '0.9'
},
{
url: `${baseUrl}/tags/`,
lastmod: new Date().toISOString(),
changefreq: 'weekly',
priority: '0.6'
}
]
const blogPosts = posts.map(post => ({
url: `${baseUrl}/blog/${post.id}/`,
lastmod: post.data.date.toISOString(),
changefreq: 'monthly',
priority: '0.7'
}))
const projectPosts = projects.map(project => ({
url: `${baseUrl}/projects/${project.id}/`,
lastmod: (project.data.endDate ?? new Date()).toISOString(),
changefreq: 'monthly',
priority: '0.6'
}))
const tagUrls = Array.from(tags, ([tag, _]) => ({
url: `${baseUrl}/tags/${tag}/`,
lastmod: new Date().toISOString(),
changefreq: 'weekly',
priority: '0.5'
}))
const allUrls = [...staticPages, ...projectPosts, ...blogPosts, ...tagUrls]
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
${allUrls
.map(
page => ` <url>
<loc>${page.url}</loc>
<lastmod>${page.lastmod}</lastmod>
<changefreq>${page.changefreq}</changefreq>
<priority>${page.priority}</priority>
</url>`
)
.join('\n')}
</urlset>`
return new Response(xml, {
headers: {
'Content-Type': 'application/xml',
'Cache-Control': 'public, max-age=3600'
}
})
} catch (error) {
console.error('Error generating sitemap:', error)
return new Response('Error generating sitemap', { status: 500 })
}
// Deprecated custom sitemap. Rely on @astrojs/sitemap integration to avoid duplicates.
export async function GET() {
return new Response('Moved to sitemap-index.xml', {
status: 301,
headers: { Location: '/sitemap-index.xml' }
})
}

View File

@@ -15,6 +15,7 @@ const currentUrl = Astro.url;
<PageHead slot="head" title="Tags" />
<Breadcrumbs items={[{ label: 'Tags', icon: 'lucide:tags' }]} />
<h1 class="text-3xl font-bold mb-4">Tags</h1>
<div class="flex flex-col gap-4">
<div class="flex flex-wrap gap-2">
{

View File

@@ -3,34 +3,25 @@
@custom-variant dark (&:is(.dark *));
@font-face {
font-family: 'Geist';
src: url('/fonts/GeistVF.woff2') format('woff2-variations');
font-weight: 100 900;
font-family: 'ClashDisplay';
src: url('/fonts/ClashDisplay/ClashDisplay-Semibold.woff2') format('woff2'),
url('/fonts/ClashDisplay/ClashDisplay-Semibold.woff') format('woff');
font-weight: bold;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Geist Mono';
src: url('/fonts/GeistMonoVF.woff2') format('woff2-variations');
font-weight: 100 900;
font-family: 'Lexend';
src: url('/fonts/Lexend/Lexend-Regular.woff2') format('woff2'),
url('/fonts/Lexend/Lexend-Regular.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'ClashDisplay-Semibold';
src:
url('/fonts/ClashDisplay-Semibold.woff2') format('woff2'),
url('/fonts/ClashDisplay-Semibold.woff') format('woff'),
url('/fonts/ClashDisplay-Semibold.ttf') format('truetype');
font-weight: 400;
font-display: swap;
font-style: normal;
}
.font-custom {
font-family: 'ClashDisplay-Semibold';
font-family: 'ClashDisplay', sans-serif;
}
:root {
@@ -68,9 +59,7 @@
--radius: 0.5rem;
--font-sans:
Geist, ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
--font-sans: "Lexend", sans-serif;
--font-mono:
Geist Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
'Liberation Mono', 'Courier New', monospace;