Compare commits
4 Commits
fca3d8bcec
...
use-yarn
| Author | SHA1 | Date | |
|---|---|---|---|
| 501bd948ca | |||
| cf0579023e | |||
| 6d3dab582a | |||
| 08db8ef124 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -25,7 +25,7 @@ yarn-debug.log*
|
|||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
# local env files
|
# local env files
|
||||||
.env*.local
|
.env*
|
||||||
|
|
||||||
# vercel
|
# vercel
|
||||||
.vercel
|
.vercel
|
||||||
|
|||||||
3
.tool-versions
Normal file
3
.tool-versions
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
nodejs 20.8.1
|
||||||
|
yarn 1.22.19
|
||||||
|
pnpm 8.9.2
|
||||||
@@ -1,11 +1,7 @@
|
|||||||
import BlogHeader from "@/components/blogHeader";
|
|
||||||
import BlogFooter from "@/components/blogFooter";
|
|
||||||
|
|
||||||
export default function About() {
|
export default function About() {
|
||||||
return(
|
return(
|
||||||
<div>
|
<div className={`flex flex-col`}>
|
||||||
<BlogHeader />
|
<p>About</p>
|
||||||
<BlogFooter />
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,7 @@
|
|||||||
import BlogHeader from "@/components/blogHeader";
|
|
||||||
import BlogFooter from "@/components/blogFooter";
|
|
||||||
|
|
||||||
export default function Blog() {
|
export default function Blog() {
|
||||||
return(
|
return(
|
||||||
<div>
|
<div className={`flex flex-col`}>
|
||||||
<BlogHeader />
|
<p>Blog Post List</p>
|
||||||
<BlogFooter />
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
21
app/dbcheck/page.tsx
Normal file
21
app/dbcheck/page.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { promisePool } from "@/backend/db";
|
||||||
|
import {RowDataPacket} from "mysql2";
|
||||||
|
|
||||||
|
async function query() {
|
||||||
|
try {
|
||||||
|
const [rows, fields] = await promisePool.query<RowDataPacket[]>('select slug from post limit 1;')
|
||||||
|
return(rows[0]['slug'] as string)
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
return('something went wrong')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function DbCheck() {
|
||||||
|
return(
|
||||||
|
<div className={`flex flex-col`}>
|
||||||
|
<p>Env: { process.env.MYSQL_HOST }</p>
|
||||||
|
<p>Result: { await query() }</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
23
app/fonts.ts
Normal file
23
app/fonts.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { Raleway, Syne, Questrial, Nunito_Sans } from "next/font/google";
|
||||||
|
|
||||||
|
export const raleway = Raleway({
|
||||||
|
subsets: ['latin'],
|
||||||
|
display: "swap",
|
||||||
|
})
|
||||||
|
|
||||||
|
export const syne = Syne({
|
||||||
|
subsets: ['latin'],
|
||||||
|
display: "swap",
|
||||||
|
})
|
||||||
|
|
||||||
|
export const questrial = Questrial({
|
||||||
|
subsets: ['latin'],
|
||||||
|
display: "swap",
|
||||||
|
weight: ['400'],
|
||||||
|
})
|
||||||
|
|
||||||
|
export const nunito_sans = Nunito_Sans({
|
||||||
|
subsets: ['latin'],
|
||||||
|
display: "swap",
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
@import url("https://fonts.googleapis.com/css?family=Raleway&display=swap");
|
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|||||||
@@ -1,22 +1,31 @@
|
|||||||
import './globals.css'
|
import "./globals.css";
|
||||||
import type { Metadata } from 'next'
|
import type { Metadata } from "next";
|
||||||
import { Inter } from 'next/font/google'
|
import { Inter } from "next/font/google";
|
||||||
|
import BlogHeader from "@/components/blogHeader";
|
||||||
|
import BlogFooter from "@/components/blogFooter";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
const inter = Inter({ subsets: ['latin'] })
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Create Next App',
|
title: "Create Next App",
|
||||||
description: 'Generated by create next app',
|
description: "Generated by create next app",
|
||||||
}
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body className={inter.className}>{children}</body>
|
<body className={inter.className}>
|
||||||
|
<div className={`flex flex-col bg-white`}>
|
||||||
|
<BlogHeader />
|
||||||
|
{children}
|
||||||
|
<BlogFooter />
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
41
app/page.tsx
41
app/page.tsx
@@ -1,29 +1,27 @@
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import BlogHeader from "../components/blogHeader";
|
import { raleway, syne, questrial } from "@/app/fonts";
|
||||||
import BlogFooter from "@/components/blogFooter";
|
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col bg-white">
|
<div className={`flex flex-col`}>
|
||||||
<BlogHeader />
|
<div className={`bg-cover bg-center flex flex-col-reverse bg-blog-cover`}>
|
||||||
<div className="bg-cover bg-center flex flex-col-reverse bg-blog-cover">
|
<div className={`bg-neutral-100 bg-opacity-30 flex flex-col py-10`}>
|
||||||
<div className="bg-neutral-100 bg-opacity-30 flex flex-col py-10">
|
<p className={`${raleway.className} text-white text-center text-7xl font-thin mb-6`}>
|
||||||
<p className="text-white text-center text-7xl font-thin font-blog mb-6">
|
|
||||||
SUYONO
|
SUYONO
|
||||||
</p>
|
</p>
|
||||||
<p className="text-white text-center font-blog font-thin text-xl mb-10">
|
<p className={`${raleway.className} text-white text-center font-thin text-xl mb-10`}>
|
||||||
A Tech Archive
|
A Tech Archive
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-64"></div> {/* spacer */}
|
<div className={`h-64`}></div> {/* spacer */}
|
||||||
<div className="h-64"></div> {/* spacer */}
|
<div className={`h-64`}></div> {/* spacer */}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row justify-center my-8">
|
<div className={`flex flex-row justify-center my-8`}>
|
||||||
<div className="border border-slate-100 flex flex-col">
|
<div className={`border border-slate-100 flex flex-col`}>
|
||||||
<Link
|
<Link
|
||||||
href="/post/nginx-ssl-client-certificate-verification-manage-access-to-a-site"
|
href="/post/nginx-ssl-client-certificate-verification-manage-access-to-a-site"
|
||||||
className="flex flex-row max-w-4xl items-center"
|
className={`flex flex-row max-w-4xl items-center`}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
src="/assets/pthumb.webp"
|
src="/assets/pthumb.webp"
|
||||||
@@ -31,12 +29,12 @@ export default function Home() {
|
|||||||
width={454}
|
width={454}
|
||||||
height={341}
|
height={341}
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-col mx-10">
|
<div className={`flex flex-col mx-10`}>
|
||||||
<p className="font-blog text-2xl">
|
<p className={`${syne.className} text-2xl`}>
|
||||||
Nginx + SSL Client Certificate Verification: Manage Access to a
|
Nginx + SSL Client Certificate Verification: Manage Access to a
|
||||||
site
|
site
|
||||||
</p>
|
</p>
|
||||||
<p className="font-blog line-clamp-3 mt-4">
|
<p className={`${questrial.className} line-clamp-3 mt-4`}>
|
||||||
Access control is a fundamental part of security. Most entities
|
Access control is a fundamental part of security. Most entities
|
||||||
rely on the combination of username and password, sometimes with
|
rely on the combination of username and password, sometimes with
|
||||||
additional multi-factor authentication to improve security. Some
|
additional multi-factor authentication to improve security. Some
|
||||||
@@ -51,13 +49,13 @@ export default function Home() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row bg-teal-50 justify-center">
|
<div className={`flex flex-row bg-teal-50 justify-center`}>
|
||||||
<div className="max-w-4xl py-28 px-10">
|
<div className={`max-w-4xl py-28 px-10`}>
|
||||||
<p className="text-3xl font-blog">Hi There</p>
|
<p className={`text-3xl ${raleway.className}`}>Hi There</p>
|
||||||
<p className="text-base font-blog my-4">
|
<p className={`text-base ${raleway.className} my-4`}>
|
||||||
a new take on experience is the best teacher
|
a new take on experience is the best teacher
|
||||||
</p>
|
</p>
|
||||||
<p className="font-blog text-sm">
|
<p className={`${raleway.className} text-sm`}>
|
||||||
I started this blog as an archive of my experiences and knowledge.
|
I started this blog as an archive of my experiences and knowledge.
|
||||||
By writing them out, I hope it will help me unlearn and relearn the
|
By writing them out, I hope it will help me unlearn and relearn the
|
||||||
various knowledge and skills I've accumulated. I hope the
|
various knowledge and skills I've accumulated. I hope the
|
||||||
@@ -66,7 +64,6 @@ export default function Home() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<BlogFooter />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
82
app/post/[slug]/page.tsx
Normal file
82
app/post/[slug]/page.tsx
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import { getPost } from "@/backend/post";
|
||||||
|
import DOMPurify from "dompurify";
|
||||||
|
import { JSDOM } from "jsdom";
|
||||||
|
import { nunito_sans, raleway } from "@/app/fonts";
|
||||||
|
import parse, {
|
||||||
|
domToReact,
|
||||||
|
Element,
|
||||||
|
HTMLReactParserOptions,
|
||||||
|
} from "html-react-parser";
|
||||||
|
import { DummyPostSlug, DummyPostString } from "@/components/dummyPost";
|
||||||
|
|
||||||
|
const options: HTMLReactParserOptions = {
|
||||||
|
replace: (domNode) => {
|
||||||
|
if (domNode instanceof Element && domNode.attribs) {
|
||||||
|
// console.log(domNode.attribs)
|
||||||
|
if (domNode.name === "h1") {
|
||||||
|
if (domNode.attribs.class === "title") {
|
||||||
|
return (
|
||||||
|
<h1 className={`${raleway.className} mx-auto w-224 text-4xl`}>
|
||||||
|
{domToReact(domNode.children)}
|
||||||
|
</h1>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<h1 className={`${raleway.className} mx-auto w-224 text-3xl`}>
|
||||||
|
{domToReact(domNode.children)}
|
||||||
|
</h1>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (domNode.name === "h2") {
|
||||||
|
return (
|
||||||
|
<h1 className={`${raleway.className} mx-auto w-224 text-2xl`}>
|
||||||
|
{domToReact(domNode.children)}
|
||||||
|
</h1>
|
||||||
|
);
|
||||||
|
} else if (domNode.name === "h3") {
|
||||||
|
return (
|
||||||
|
<h1 className={`${raleway.className} mx-auto w-224 text-xl`}>
|
||||||
|
{domToReact(domNode.children)}
|
||||||
|
</h1>
|
||||||
|
);
|
||||||
|
} else if (domNode.name === "p") {
|
||||||
|
if (domNode.attribs.class === "paragraph") {
|
||||||
|
return (
|
||||||
|
<h1 className={`${nunito_sans.className} mx-auto w-224`}>
|
||||||
|
{domToReact(domNode.children)}
|
||||||
|
</h1>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const classes = domNode.attribs.class.split(" ");
|
||||||
|
if (classes.includes("code")) {
|
||||||
|
if (classes.includes("shell")) {
|
||||||
|
|
||||||
|
} else if (classes.includes("go") || classes.includes("golang")) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function Post({ params }: { params: { slug: string } }) {
|
||||||
|
let content = "";
|
||||||
|
|
||||||
|
const dummySlug = await DummyPostSlug();
|
||||||
|
if (dummySlug === params.slug) {
|
||||||
|
content = await DummyPostString();
|
||||||
|
console.log(content);
|
||||||
|
} else {
|
||||||
|
content = await getPost(params.slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
content = DOMPurify(new JSDOM("<!DOCTYPE html>").window).sanitize(content);
|
||||||
|
// console.log(content)
|
||||||
|
const elem = parse(content, options);
|
||||||
|
|
||||||
|
return <div className={`flex flex-col`}>{elem}</div>;
|
||||||
|
}
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import BlogHeader from "@/components/blogHeader";
|
|
||||||
import BlogFooter from "@/components/blogFooter";
|
|
||||||
|
|
||||||
export default function Post1() {
|
|
||||||
return(
|
|
||||||
<div>
|
|
||||||
<BlogHeader />
|
|
||||||
<BlogFooter />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
20
backend/db.ts
Normal file
20
backend/db.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import mysql, { PoolOptions } from "mysql2";
|
||||||
|
|
||||||
|
const access: PoolOptions = {
|
||||||
|
host: process.env.MYSQL_HOST,
|
||||||
|
port: 'MYSQL_PORT' in process.env && typeof process.env.MYSQL_PORT === 'string' ? parseInt(process.env.MYSQL_PORT) : 3306,
|
||||||
|
user: process.env.MYSQL_USER,
|
||||||
|
password: process.env.MYSQL_PASSWORD,
|
||||||
|
database: 'MYSQL_DATABASE' in process.env ? process.env.MYSQL_DATABASE : 'blog',
|
||||||
|
waitForConnections: true,
|
||||||
|
connectionLimit: 10,
|
||||||
|
maxIdle: 10,
|
||||||
|
idleTimeout: 60000,
|
||||||
|
queueLimit: 0,
|
||||||
|
enableKeepAlive: true,
|
||||||
|
keepAliveInitialDelay: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const pool = mysql.createPool(access)
|
||||||
|
|
||||||
|
export const promisePool = pool.promise()
|
||||||
13
backend/post.ts
Normal file
13
backend/post.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { RowDataPacket } from "mysql2";
|
||||||
|
import { promisePool } from "@/backend/db";
|
||||||
|
|
||||||
|
export async function getPost(slug: string): Promise<string> {
|
||||||
|
try {
|
||||||
|
const [rows, fields] = await promisePool.query<RowDataPacket[]>(
|
||||||
|
'select content from post where slug = ?', [slug])
|
||||||
|
return rows[0]['content']
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
|
import {raleway} from "@/app/fonts";
|
||||||
|
|
||||||
export default function BlogFooter() {
|
export default function BlogFooter() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p className="text-center font-blog text-xl my-10">Suyono</p>
|
<p className={`${raleway.className} text-center text-xl my-10`}>Suyono</p>
|
||||||
<p className="text-center font-blog">suyono3484@gmail.com</p>
|
<p className={`${raleway.className} text-center`}>suyono3484@gmail.com</p>
|
||||||
<p className="text-center font-blog mt-20 mb-10">
|
<p className={`${raleway.className} text-center mt-20 mb-10`}>
|
||||||
©2023 by Suyono. Built using Next.js
|
©2023 by Suyono. Built using Next.js
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,21 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { raleway }from "@/app/fonts";
|
||||||
|
|
||||||
export default function BlogHeader() {
|
export default function BlogHeader() {
|
||||||
return(
|
return(
|
||||||
<div>
|
<div>
|
||||||
<div className="ml-20 py-8">
|
<div className="ml-20 py-8">
|
||||||
<p className="font-blog text-2xl font-thin">SUYONO</p>
|
<p className={`${raleway.className} text-2xl font-thin`}>SUYONO</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-gray-100">
|
<div className="bg-gray-100">
|
||||||
<div className="flex flex-row ml-20">
|
<div className="flex flex-row ml-20">
|
||||||
<Link href="/" className="m-2 font-thin text-sm font-blog">
|
<Link href="/" className={`${raleway.className} m-2 font-thin text-sm`}>
|
||||||
Home
|
Home
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/about" className="m-2 font-thin text-sm font-blog">
|
<Link href="/about" className={`${raleway.className} m-2 font-thin text-sm`}>
|
||||||
About
|
About
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/blog" className="m-2 font-thin text-sm font-blog">
|
<Link href="/blog" className={`${raleway.className} m-2 font-thin text-sm`}>
|
||||||
Blog
|
Blog
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
78
components/dummyPost.tsx
Normal file
78
components/dummyPost.tsx
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
export async function DummyPostString() {
|
||||||
|
const ReactDOMServer = (await import('react-dom/server')).default
|
||||||
|
const component = await DummyPost()
|
||||||
|
return ReactDOMServer.renderToStaticMarkup(component)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function DummyPostSlug() {
|
||||||
|
return "dummy-post"
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function DummyPost() {
|
||||||
|
return(
|
||||||
|
<div>
|
||||||
|
<h1 className="title">Nginx + SSL Client Certificate Verification: Manage access to a site</h1>
|
||||||
|
<p className="paragraph">Access control is a fundamental part of security. Most entities rely on
|
||||||
|
the combination of username and password, sometimes with additional multi-factor authentication
|
||||||
|
to improve security. Some entities also use the SSL client certificate verification to manage access
|
||||||
|
to specific resources. One of the use cases where SSL client certificate verification fits perfectly is
|
||||||
|
managing access to internet-facing development or staging servers. In this post, I'll share how
|
||||||
|
to set up the certificates and configure nginx to verify users based on their certificates.</p>
|
||||||
|
<h1>Preparing the certificates</h1>
|
||||||
|
<p className="paragraph">There are two certificates we are going to create. The first one is the root
|
||||||
|
certificate. It will be placed in the Nginx server. The second one is the client certificate. It will
|
||||||
|
be installed in the client machine/browsers.</p>
|
||||||
|
<h2>Root CA</h2>
|
||||||
|
<p className="paragraph">For generating a root CA, execute these two steps:</p>
|
||||||
|
<h3>Generate RSA Key</h3>
|
||||||
|
<p className="code">openssl genrsa -aes256 -out ca.key 4096</p>
|
||||||
|
<h3>Create Root CA crt file.</h3>
|
||||||
|
<p className="code">openssl req -new -x509 -days 3650 -key ca.key -out ca.crt</p>
|
||||||
|
<h2>Setup CA configuration</h2>
|
||||||
|
<p className="paragraph">This is an optional step, but if you want to be able to revoke access you
|
||||||
|
previously granted, you need to do this step.</p>
|
||||||
|
<p className="paragraph">Create a file named ca.cnf in the same directory as the ca.key and ca.crt.</p>
|
||||||
|
<p className="code">[ ca ]
|
||||||
|
default_ca = gca
|
||||||
|
|
||||||
|
[ crl_ext ]
|
||||||
|
authorityKeyIdentifier=keyid:always
|
||||||
|
|
||||||
|
[ gca ]
|
||||||
|
dir = ./
|
||||||
|
new_certs_dir = $dir
|
||||||
|
unique_subject = no
|
||||||
|
certificate = $dir/ca.crt
|
||||||
|
database = $dir/certindex
|
||||||
|
private_key = $dir/ca.key
|
||||||
|
serial = $dir/certserial
|
||||||
|
default_days = 365
|
||||||
|
default_md = sha256
|
||||||
|
policy = gca_policy
|
||||||
|
x509_extensions = gca_extensions
|
||||||
|
crlnumber = $dir/crlnumber
|
||||||
|
default_crl_days = 365
|
||||||
|
|
||||||
|
[ gca_policy ]
|
||||||
|
commonName = supplied
|
||||||
|
stateOrProvinceName = supplied
|
||||||
|
countryName = optional
|
||||||
|
emailAddress = optional
|
||||||
|
organizationName = supplied
|
||||||
|
organizationUnitName = optional
|
||||||
|
|
||||||
|
[ gca_extensions ]
|
||||||
|
basicConstraints = CA:false
|
||||||
|
subjectKeyIdentifier = hash
|
||||||
|
authorityKeyIdentifier = keyid:always
|
||||||
|
keyUsage = digitalSignature,keyEncipherment
|
||||||
|
extendedKeyUsage = serverAuth
|
||||||
|
crlDistributionPoints = URI:http://example.com/root.crl
|
||||||
|
subjectAltName = @alt_names
|
||||||
|
|
||||||
|
[ alt_names ]
|
||||||
|
DNS.1 = example.com
|
||||||
|
DNS.2 = *.example.com</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {}
|
const nextConfig = {
|
||||||
|
webpack: (config) => {
|
||||||
|
config.externals = [...config.externals, "jsdom"];
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = nextConfig
|
module.exports = nextConfig
|
||||||
|
|||||||
@@ -9,16 +9,23 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/dompurify": "^3.0.3",
|
||||||
|
"@types/jsdom": "^21.1.3",
|
||||||
"@types/node": "20.6.5",
|
"@types/node": "20.6.5",
|
||||||
"@types/react": "18.2.22",
|
"@types/react": "18.2.22",
|
||||||
"@types/react-dom": "18.2.7",
|
"@types/react-dom": "18.2.7",
|
||||||
"autoprefixer": "10.4.16",
|
"autoprefixer": "10.4.16",
|
||||||
|
"dompurify": "^3.0.6",
|
||||||
"eslint": "8.50.0",
|
"eslint": "8.50.0",
|
||||||
"eslint-config-next": "13.5.2",
|
"eslint-config-next": "13.5.2",
|
||||||
|
"html-react-parser": "^4.2.2",
|
||||||
|
"jsdom": "^22.1.0",
|
||||||
|
"mysql2": "^3.6.1",
|
||||||
"next": "13.5.2",
|
"next": "13.5.2",
|
||||||
"postcss": "8.4.30",
|
"postcss": "8.4.30",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
|
"redis": "^4.6.10",
|
||||||
"tailwindcss": "3.3.3",
|
"tailwindcss": "3.3.3",
|
||||||
"typescript": "5.2.2"
|
"typescript": "5.2.2"
|
||||||
},
|
},
|
||||||
|
|||||||
2631
pnpm-lock.yaml
generated
2631
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,5 @@
|
|||||||
import type { Config } from 'tailwindcss'
|
import type { Config } from 'tailwindcss'
|
||||||
|
|
||||||
import { fontFamily } from 'tailwindcss/defaultTheme'
|
|
||||||
|
|
||||||
const config: Config = {
|
const config: Config = {
|
||||||
content: [
|
content: [
|
||||||
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
|
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
|
||||||
@@ -16,9 +14,9 @@ const config: Config = {
|
|||||||
'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
|
'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
|
||||||
'blog-cover': "url('/assets/placeholder.webp')"
|
'blog-cover': "url('/assets/placeholder.webp')"
|
||||||
},
|
},
|
||||||
fontFamily: {
|
width: {
|
||||||
'blog': ['Raleway', ...fontFamily.sans],
|
'224': '56rem',
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
|
|||||||
Reference in New Issue
Block a user