Como eu criei meu próprio LinkTree com GoogleSheets no Banco de Dados

Olá amigos, este é a minha primeira publicação e gostaria de contribuir com algo útil para a comunidade, com uma solução útil e barata para o problema de compartilhamento de links.

UMA BREVE INTRODUÇÃO Em poucas palavras, o linkTree é uma ferramenta que auxilia no compartilhamento de links. Este serviço já está consolidado no mercado como um dos maiores da internet, e também existem soluções competitivas com preços variados.

Ademais, este serviço conta com diversas funcionalidade como analytics, ícones de redes sociais, bem como exibição dos útlimos vídeos do youtube e posts do X (antigo twitter).

Ocorre que o linktree apesar de útil, é pouco personalizável para os planos gratuitos, além de contar com poucos recursos, o que desestimula a utilização por longo prazo.

Pensando nisso eu imaginei como eu solucionaria a minha dor da vida real, com poucos recursos, gratuitos e fáceis de implementar.

DA ESCOLHA DA LINGUAGEM (FW) E BANCO DE DADOS Eu sou advogado e estudante de Ciência da Computação e meu tempo é bastante escasso, em virtude disto eu precisava de algo com escalabilidade, de fácil manutenção e barato. Por estas razões eu decidi utilizar Next.js, devido eu já ter bastante afinidade com Javascript, e utilizar Google Sheets como repositório dos meus links, configurações e demais recursos.

A idéia de usar o GSheets como banco de dados é antiga, e a documentação é bastante fácil de entender, não é necessário criar tokens de autorização, nem nada mais complexo, somente autorizar no navegador e TANDAN tudo funcionando.

VAMOS AO CÓDIGO Comecei modelando como ficaria o "Banco de Dados", e decidi deixá-lo o mais flexível possível, transformando em uma API somente com o verbo GET, pois o create, delete e update ficaria a cargo das modificações manuais no GSheet.

E seguindo a documentação, criando funções com responsabilidades específicas consegui criar um código organizado.

/**
 * Retorna todas as planilhas
 * @returns {GoogleAppsScript.Spreadsheet.Sheet}
 */
function getPages() {
    return SpreadsheetApp.getActiveSpreadsheet().getSheets()
}

/**
 * Converte um array em objetos, usando a primeira linha como chaves.
 * @param {Array} dados - Um array de dados a ser convertido em objetos.
 * @returns {Array} - Um array de objetos convertido a partir dos dados fornecidos.
 */
function arrayToObject(dados) {
    const keys = dados[0];
    return dados.slice(1).map((values) => {
        return keys.reduce((obj, key, index) => {
            // Verifica se a chave começa com '#'
            if (!key.startsWith("#")) {
                const value = values[index];
                obj[key] = String(value);
            }
            return obj;
        }, {});
    })
}

/**
 * Converte um objeto JavaScript em uma resposta JSON.
 * @param {Object} responseData - Os dados a serem convertidos para JSON.
 * @returns {ContentService.TextOutput} - O objeto JSON como uma saída de texto.
 */
function jsonReturn(responseData) {
    return ContentService.createTextOutput(JSON.stringify(responseData))
        .setMimeType(ContentService.MimeType.JSON)
}

/**
 * Manipula as operações GET na API, retornando dados com base nos parâmetros fornecidos.
 * @param {Object} e - O objeto de evento contendo os parâmetros da solicitação GET.
 * @returns {ContentService.TextOutput} - O resultado da operação em formato JSON.
 */
function doGet(e) {
    const sheetNames = getPages().map(page => page.getName())

    const data = getPages().map((page, index) => {
        const key = sheetNames[index]
        const rawData = page.getDataRange().getValues()
        const arrayData = arrayToObject(rawData)
        return { [key]: arrayData }
    })

    return jsonReturn(data)
}
    

Startei meu projeto com Next.js, seguindo a documentação e criei uma rota para Api em src\api

    export async function getConfigs() {
        const res = await fetch(process.env.API_ADDRESS, { cache: 'force-cache' })

        if (!res.ok) {
            return {
                description: "Erro ao recuperar dados da api",
            }
        }

        const [, , { configuracao }] = await res.json()
        return configuracao
    }

    export default async function getLinks() {
        const res = await fetch(process.env.API_ADDRESS, { cache: 'force-cache' })

        if (!res.ok) {
            return {
                description: "Erro ao recuperar dados da api",
            }
        }

        return res.json()
    }

Decidi deixar os dados em cache para ter uma melhor resposta nos Speed Insights da Vercel.

Também decidir utilizar lazyLoading para quando eu decidisse atualizar os links ou adicionar mais recursos, para não ter que deixar o usuário aguardando o push do GSheets (É muito lento 😞).

Criei meus componentes, usei pela primeira vez Tailwind e senti facilidade, pois tudo era familiar com os comandos de CSS3.

Uma sacada que eu tive foi de tentar deixar os ícones das redes sociais de forma flexível, colocando as redes sociais mais utilizadas, e outros ícones que podem ser puxados do React-Icons.

Emojis são renderizados perfeitamente.

E finalmente eu publiquei e deixei o repositório aberto https://github.com/heraclitothiago/links

O Resultado final foi esse e fiquei muito orgulhoso com o resultado, visto que sou programador inexperiente. Banner

Apreciarei muito se visitarem o projeto pelo meu instagram, para eu visualizar os Analytics da Vercel que foram implementados https://instagram.com/thiagocastro.adv

Estou aberto a sugestões de melhorias do meu código e do meu projetinho. https://github.com/heraclitothiago/links

Parabéns pelo projeto. Programação é a arte de resolver problemas. E parabéns pela documentação e compartilhamento.

Parabéns pela solução criativa, achei muito legal, principalmente sobre o uso do GSheet, isso nunca passou pela mibha cabeça kkk.

Se quiser testar uma boa solução gratuita, não faz muito tempo criei essa ferramenta https://navto.me

A conta gratuita para sempre conta alguns layouts disponíveis e pode personalizar cores estilo de lista, adionar videos e música, confere lá

Uma dúvida, como vai essa sua aplicação? O que você achou da experiência de desenvolver o serviço (não sei o seu nivel de senioridade) e, talvez sendo indiscreto, como está o rendimento dele?
Obrigado pela dica. Vou conferir sua recomendação e talvez até incluir recursos que não estão disponíveis no meu projeto =)