Closed1
Qwik + vite + express, CMS表示系の作成
概要
Qwik + express SSR(react-dom/server)で、CMS表示のメモになります。
- 主に、SSRになります。一部CSRになります。
- 編集機能は、前の複数blog対応 Headless CMSと連携します (CF-pages)
- テストは、vercelにデプロイする形です。
[ 公開: 2024/03/02 ]
環境
- Qwik
- express
- vite: 5
- esbuid
- vercel
関連
- 前のexpress 構成関連です。
作成したコード
-
詳細 : MD変換ライブラリ使います。
-
pages/posts/show/App.tsx
App.tsx
export default function PostShow(props: any) {
console.log("#taskShow");
//console.log(props);
const content = marked.parse(props.item.content);
//console.log(content);
//
return (
<Layout title="Show">
<div className="post_show_wrap container bg-white mx-auto mt-14 mb-8 px-8 py-4">
<a href="/" className="btn-outline-purple ms-2 my-0"
>back</a>
<hr className="my-4" />
<div id="root"></div>
<h1 className="text-4xl font-bold">{props.item.title}</h1>
<p>id: {props.item.id}, {props.item.createdAt}</p>
<hr />
<div dangerouslySetInnerHTML={{ __html: content }} id="content_html"
className="mb-8" />
<hr className="my-12" />
</div>
<style>{`
.post_show_wrap {min-height : 600px;}
`}
</style>
</Layout>
)
};
- Top: pages/posts/App.tsx
App.tsx
export default function Page(props: any) {
//console.log(props.site);
if(props.page){
nextPage = Number(props.page) + 1;
beforePage = Number(props.page) - 1;
if(beforePage <= 1) { beforePage = 1;}
}
//
return (
<Layout>
<div className="text-center py-16 bg-gray-400 text-white mt-10">
<h1 className="text-4xl font-bold">{props.site.name}</h1>
</div>
<input type="text" className="d-none" id="item_id" defaultValue={props.id} />
<div className="col-md-6 text-end bg-white py-1">
<span className="search_key_wrap">
<input type="text"
className="mx-2 border border-gray-400 rounded-md px-3 py-2 focus:outline-none focus:border-blue-500"
name="searchKey" id="searchKey"
placeholder="Title search" />
</span>
<button className="ms-2 btn-outline-purple" id="btn_search"
>Search</button>
</div>
{/* post_list_wrap */}
<div className="post_list_wrap container mx-auto my-2 px-2">
{props.items.map((item: any) => {
return (
<div key={item.id} className="rounded-md bg-white my-2 p-4">
<div className="flex flex-row">
<div className="flex-1 p-2 m-1">
<a href={`/posts/${item.id}`}><h3 className="text-3xl font-bold"
>{item.title}</h3></a>
<p>ID: {item.id}, {item.createdAt}</p>
</div>
<div className="flex-1 p-2 m-1 text-end">
<a href={`/posts/${item.id}`}>
<button className="btn-outline-purple ms-2 my-2">Show</button>
</a>
</div>
</div>
</div>
);
})}
</div>
<div id="app"></div>
{(process.env.NODE_ENV === "develop") ? (
<script type="module" src="/static/Top.js"></script>
): (
<script type="module" src="/public/static/Top.js"></script>
)}
<hr className="my-8" />
<style>{`
.post_list_wrap {min-height: 500px;}
`}</style>
</Layout>
)
}
- src/index.ts
- headless CMS APIと、連携します。
- API接続先は、 .envから取得します。
index.ts
import express from 'express';
import { renderToString } from 'react-dom/server';
const app = express();
....
import PostsIndex from './pages/posts/App';
import PostsShow from './pages/posts/show/App';
app.get('/',async (req: any, res: any) => {
try {
const site = await siteRouter.get();
const items = await postRouter.get_list_page(Number(1));
res.send(renderToString(PostsIndex({items: items, page: 1, site: site})));
} catch (error) { res.sendStatus(500); }
});
- front : client/Top/app.tsx, 検索部分
- 外部API連携、検索してます。
app.tsx
import { component$, useSignal, useStore, useComputed$, useTask$, $ } from '@builder.io/qwik';
....
export const App = component$(() => {
const text = useSignal('qwik');
const state = useStore({ count: 0, items: [] });
const count = useSignal(0)
const time = useSignal('paused');
//init
useTask$(({ track, cleanup }) => {
const btn_search = document.querySelector('#btn_search');
btn_search?.addEventListener('click', async () => {
const post_list_wrap = document.querySelector(`.post_list_wrap`) as HTMLInputElement;
if (!post_list_wrap.classList.contains('d-none')) {
post_list_wrap?.classList.add('d-none');
}
const res = await CrudIndex.search();
state.items = res;
console.log(res);
});
});
//
return (
<>
<div class="search_result_wrap container mx-auto my-2 px-2">
<hr class="my-2" />
{state.items.map((item: any) => {
return (
<div key={item.id} class="rounded-md bg-white my-2 p-4">
<div class="flex flex-row">
<div class="flex-1 p-2 m-1">
<a href={`/posts/${item.id}`} target="_blank"><h3 class="text-3xl font-bold"
>{item.title}</h3></a>
<p>ID: {item.id}, {item.createdAt}</p>
</div>
<div class="flex-1 p-2 m-1 text-end">
<a href={`/posts/${item.id}`} target="_blank">
<button class="btn-outline-purple ms-2 my-2">Show</button>
</a>
</div>
</div>
</div>
);
})}
</div>
</>
)
})
.env
VITE_SITE_ID=1
VITE_API_URL=http://localhost
VITE_API_KEY="123"
このスクラップは2024/04/14にクローズされました